You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2017/03/28 06:04:47 UTC

[4/5] groovy git commit: CR fixes, split code into smaller parts, add @Incubating and @since to different classes

CR fixes, split code into smaller parts, add @Incubating and @since to different classes


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/30949043
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/30949043
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/30949043

Branch: refs/heads/master
Commit: 30949043538a813e78c8e334f07bfe8e79e953cb
Parents: ada0e2c
Author: Sergei Egorov <se...@zeroturnaround.com>
Authored: Sat Mar 25 16:44:48 2017 +0200
Committer: paulk <pa...@asert.com.au>
Committed: Tue Mar 28 16:02:26 2017 +1000

----------------------------------------------------------------------
 .../codehaus/groovy/macro/runtime/Macro.java    |   1 +
 .../groovy/macro/runtime/MacroBuilder.java      |   6 +
 .../groovy/macro/runtime/MacroContext.java      |  15 ++
 .../groovy/macro/runtime/MacroStub.java         |   1 +
 .../transform/MacroCallTransformingVisitor.java | 126 +++++++++++-----
 .../groovy/macro/transform/MacroClass.java      |   8 ++
 .../transform/MacroClassTransformation.java     | 142 +++++++++++--------
 .../macro/transform/MacroMethodsCache.java      |   1 +
 .../macro/transform/MacroTransformation.java    |   1 +
 9 files changed, 205 insertions(+), 96 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/30949043/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/Macro.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/Macro.java b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/Macro.java
index b6a8938..a0fa4fc 100644
--- a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/Macro.java
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/Macro.java
@@ -27,6 +27,7 @@ import java.lang.annotation.Target;
 
 /**
  * @author Sergei Egorov <bs...@gmail.com>
+ * @since 2.5.0
  */
 
 @Retention(RetentionPolicy.RUNTIME)

http://git-wip-us.apache.org/repos/asf/groovy/blob/30949043/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroBuilder.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroBuilder.java b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroBuilder.java
index ef857e9..73cb0e2 100644
--- a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroBuilder.java
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroBuilder.java
@@ -19,6 +19,7 @@
 package org.codehaus.groovy.macro.runtime;
 
 import groovy.lang.Closure;
+import org.apache.groovy.lang.annotation.Incubating;
 import org.codehaus.groovy.ast.ASTNode;
 import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
 import org.codehaus.groovy.ast.ClassNode;
@@ -38,8 +39,13 @@ import java.util.concurrent.atomic.AtomicInteger;
 import static org.codehaus.groovy.macro.methods.MacroGroovyMethods.DOLLAR_VALUE;
 
 /**
+ * Runtime support for <pre>{@code macro {} }</pre> method.
+ *
  * @author Sergei Egorov <bs...@gmail.com>
+ * @since 2.5.0
  */
+
+@Incubating
 public enum MacroBuilder {
     INSTANCE;
 

http://git-wip-us.apache.org/repos/asf/groovy/blob/30949043/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroContext.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroContext.java b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroContext.java
index e181a2a..07df293 100644
--- a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroContext.java
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroContext.java
@@ -24,7 +24,10 @@ import org.codehaus.groovy.control.CompilationUnit;
 import org.codehaus.groovy.control.SourceUnit;
 
 /**
+ * Macro method context. Every macro method must be an extension method of it.
+ *
  * @author Sergei Egorov <bs...@gmail.com>
+ * @since 2.5.0
  */
 
 @Incubating
@@ -42,14 +45,26 @@ public class MacroContext {
         this.call = call;
     }
 
+    /**
+     *
+     * @return original method call expression
+     */
     public MethodCallExpression getCall() {
         return call;
     }
 
+    /**
+     *
+     * @return current source unit
+     */
     public SourceUnit getSourceUnit() {
         return sourceUnit;
     }
 
+    /**
+     *
+     * @return current compilation unit
+     */
     public CompilationUnit getCompilationUnit() {
         return compilationUnit;
     }

http://git-wip-us.apache.org/repos/asf/groovy/blob/30949043/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroStub.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroStub.java b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroStub.java
index b0590b5..cf4b2f7 100644
--- a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroStub.java
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/runtime/MacroStub.java
@@ -22,6 +22,7 @@ package org.codehaus.groovy.macro.runtime;
  * Stub for macro calls.
  *
  * @author Sergei Egorov <bs...@gmail.com>
+ * @since 2.5.0
  */
 public enum MacroStub {
     INSTANCE;

http://git-wip-us.apache.org/repos/asf/groovy/blob/30949043/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroCallTransformingVisitor.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroCallTransformingVisitor.java b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroCallTransformingVisitor.java
index 6af1afe..1afbeb7 100644
--- a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroCallTransformingVisitor.java
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroCallTransformingVisitor.java
@@ -1,3 +1,21 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
 package org.codehaus.groovy.macro.transform;
 
 import org.codehaus.groovy.ast.*;
@@ -11,9 +29,19 @@ import org.codehaus.groovy.runtime.InvokerHelper;
 import org.codehaus.groovy.transform.stc.ExtensionMethodNode;
 import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
 
-import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
+/**
+ * Visitor to find and transform macro method calls. For the performance reasons it's not a transformer,
+ * but transforming visitor - it mutates {@link MethodCallExpression} if it's a macro method call by replacing
+ * original call (i.e. {@code myMacroMethod("foo", "bar")} with something like:
+ * {@code MacroStub.INSTANCE.macroMethod(123)}
+ * (where {@code myMacroMethod} returns constant expression {@code 123})
+ *
+ * @author Sergei Egorov <bs...@gmail.com>
+ * @since 2.5.0
+ */
 class MacroCallTransformingVisitor extends ClassCodeVisitorSupport {
 
     private static final ClassNode MACRO_CONTEXT_CLASS_NODE = ClassHelper.make(MacroContext.class);
@@ -43,58 +71,82 @@ class MacroCallTransformingVisitor extends ClassCodeVisitorSupport {
     public void visitMethodCallExpression(MethodCallExpression call) {
         super.visitMethodCallExpression(call);
 
-        List<MethodNode> methods = MacroMethodsCache.get(classLoader).get(call.getMethodAsString());
-
-        if (methods == null) {
-            // Not a macro call
-            return;
-        }
-
         List<Expression> callArguments = InvocationWriter.makeArgumentList(call.getArguments()).getExpressions();
 
-        ClassNode[] argumentsList = new ClassNode[callArguments.size()];
+        List<MethodNode> macroMethods = findMacroMethods(call.getMethodAsString(), callArguments);
 
-        for (int i = 0; i < callArguments.size(); i++) {
-            argumentsList[i] = ClassHelper.make(callArguments.get(i).getClass());
+        if (macroMethods.isEmpty()) {
+            // Early return to avoid macro context and arguments creation
+            return;
         }
 
-        methods = StaticTypeCheckingSupport.chooseBestMethod(MACRO_CONTEXT_CLASS_NODE, methods, argumentsList);
+        MacroContext macroContext = new MacroContext(unit, sourceUnit, call);
 
-        for (MethodNode macroMethodNode : methods) {
+        Object[] macroArguments = new Object[callArguments.size() + 1];
+        macroArguments[0] = macroContext;
+        System.arraycopy(callArguments.toArray(), 0, macroArguments, 1, callArguments.size());
+
+        for (MethodNode macroMethodNode : macroMethods) {
             if (!(macroMethodNode instanceof ExtensionMethodNode)) {
-                // TODO is it even possible?
-                continue;
+                throw new IllegalStateException(macroMethodNode + " is not an instance of ExtensionMethodNode");
             }
 
-            MethodNode macroExtensionMethodNode = ((ExtensionMethodNode) macroMethodNode).getExtensionMethodNode();
-
-            final Class clazz;
-            try {
-                clazz = classLoader.loadClass(macroExtensionMethodNode.getDeclaringClass().getName());
-            } catch (ClassNotFoundException e) {
-                //TODO different reaction?
-                continue;
+            if (tryMacroMethod(call, (ExtensionMethodNode) macroMethodNode, macroArguments)) {
+                break;
             }
+        }
+    }
 
-            MacroContext macroContext = new MacroContext(unit, sourceUnit, call);
+    /**
+     * Finds all extension methods of {@link MacroContext} for given methodName
+     * with @{@link org.codehaus.groovy.macro.runtime.Macro} annotation.
+     */
+    private List<MethodNode> findMacroMethods(String methodName, List<Expression> callArguments) {
+        List<MethodNode> methods = MacroMethodsCache.get(classLoader).get(methodName);
 
-            List<Object> macroArguments = new ArrayList<>();
-            macroArguments.add(macroContext);
-            macroArguments.addAll(callArguments);
+        if (methods == null) {
+            // Not a macro call
+            return Collections.emptyList();
+        }
 
-            Expression result = (Expression) InvokerHelper.invokeStaticMethod(clazz, macroMethodNode.getName(), macroArguments.toArray());
+        ClassNode[] argumentsList = new ClassNode[callArguments.size()];
 
-            call.setObjectExpression(MACRO_STUB_INSTANCE);
-            call.setMethod(new ConstantExpression(MACRO_STUB_METHOD_NAME));
+        for (int i = 0; i < callArguments.size(); i++) {
+            argumentsList[i] = ClassHelper.make(callArguments.get(i).getClass());
+        }
 
-            // TODO check that we reset everything here
-            call.setSpreadSafe(false);
-            call.setSafe(false);
-            call.setImplicitThis(false);
-            call.setArguments(result);
-            call.setGenericsTypes(new GenericsType[0]);
+        return StaticTypeCheckingSupport.chooseBestMethod(MACRO_CONTEXT_CLASS_NODE, methods, argumentsList);
+    }
 
-            break;
+    /**
+     * Attempts to call given macroMethod
+     * @param call MethodCallExpression before the transformation
+     * @param macroMethod a macro method candidate
+     * @param macroArguments arguments to pass to
+     * @return true if call succeeded and current call was transformed
+     */
+    private boolean tryMacroMethod(MethodCallExpression call, ExtensionMethodNode macroMethod, Object[] macroArguments) {
+        Expression result = (Expression) InvokerHelper.invokeStaticMethod(
+            macroMethod.getExtensionMethodNode().getDeclaringClass().getTypeClass(),
+            macroMethod.getName(),
+            macroArguments
+        );
+
+        if (result == null) {
+            // Allow macro methods to return null as an indicator that they didn't match a method call
+            return false;
         }
+
+        call.setObjectExpression(MACRO_STUB_INSTANCE);
+        call.setMethod(new ConstantExpression(MACRO_STUB_METHOD_NAME));
+
+        // TODO check that we reset everything here
+        call.setSpreadSafe(false);
+        call.setSafe(false);
+        call.setImplicitThis(false);
+        call.setArguments(result);
+        call.setGenericsTypes(new GenericsType[0]);
+
+        return true;
     }
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/30949043/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroClass.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroClass.java b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroClass.java
index 0b01efa..d9d3f7c 100644
--- a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroClass.java
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroClass.java
@@ -18,5 +18,13 @@
  */
 package org.codehaus.groovy.macro.transform;
 
+import org.apache.groovy.lang.annotation.Incubating;
+
+/**
+ *
+ * since 2.5.0
+ */
+
+@Incubating
 public abstract class MacroClass {
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/30949043/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroClassTransformation.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroClassTransformation.java b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroClassTransformation.java
index 292d59d..4ecd42e 100644
--- a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroClassTransformation.java
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroClassTransformation.java
@@ -34,6 +34,12 @@ import java.util.List;
 
 import static org.codehaus.groovy.ast.tools.GeneralUtils.*;
 
+/**
+ * Transforms {@link MacroClass} calls into it's ClassNode
+ *
+ * @since 2.5.0
+ */
+
 @GroovyASTTransformation(phase = CompilePhase.CONVERSION)
 public class MacroClassTransformation extends MethodCallTransformation {
 
@@ -42,73 +48,91 @@ public class MacroClassTransformation extends MethodCallTransformation {
 
     @Override
     protected GroovyCodeVisitor getTransformer(final ASTNode[] nodes, final SourceUnit sourceUnit) {
-        ClassCodeExpressionTransformer transformer = new ClassCodeExpressionTransformer() {
-            @Override
-            protected SourceUnit getSourceUnit() {
-                return sourceUnit;
-            }
+        ClassCodeExpressionTransformer transformer = new MacroClassTransformer(sourceUnit);
 
-            @Override
-            public Expression transform(final Expression exp) {
-                if (exp instanceof ConstructorCallExpression) {
-                    MethodCallExpression call = exp.getNodeMetaData(MacroTransformation.class);
-                    if (call != null) {
-                        return call;
-                    }
+        return new MacroClassTransformingCodeVisitor(transformer, sourceUnit);
+    }
+
+    private static class MacroClassTransformer extends ClassCodeExpressionTransformer {
+
+        private final SourceUnit sourceUnit;
+
+        public MacroClassTransformer(SourceUnit sourceUnit) {
+            this.sourceUnit = sourceUnit;
+        }
+
+        @Override
+        protected SourceUnit getSourceUnit() {
+            return sourceUnit;
+        }
+
+        @Override
+        public Expression transform(final Expression exp) {
+            if (exp instanceof ConstructorCallExpression) {
+                MethodCallExpression call = exp.getNodeMetaData(MacroTransformation.class);
+                if (call != null) {
+                    return call;
                 }
-                return super.transform(exp);
             }
-        };
-
-        return new TransformingCodeVisitor(transformer) {
-
-            @Override
-            public void visitConstructorCallExpression(final ConstructorCallExpression call) {
-                ClassNode type = call.getType();
-                if (type instanceof InnerClassNode) {
-                    if (((InnerClassNode) type).isAnonymous() &&
-                            MACROCLASS_TYPE.getNameWithoutPackage().equals(type.getSuperClass().getNameWithoutPackage())) {
-                        try {
-                            String source = convertInnerClassToSource(type);
-
-                            MethodCallExpression macroCall = callX(
-                                    propX(classX(ClassHelper.makeWithoutCaching(MacroBuilder.class, false)), "INSTANCE"),
-                                    MACRO_METHOD,
-                                    args(
-                                            constX(source),
-                                            MacroGroovyMethods.buildSubstitutionMap(sourceUnit, type),
-                                            classX(ClassHelper.make(ClassNode.class))
-                                    )
-                            );
-
-                            macroCall.setSpreadSafe(false);
-                            macroCall.setSafe(false);
-                            macroCall.setImplicitThis(false);
-                            call.putNodeMetaData(MacroTransformation.class, macroCall);
-                            List<ClassNode> classes = sourceUnit.getAST().getClasses();
-                            for (Iterator<ClassNode> iterator = classes.iterator(); iterator.hasNext(); ) {
-                                final ClassNode aClass = iterator.next();
-                                if (aClass == type || type == aClass.getOuterClass()) {
-                                    iterator.remove();
-                                }
+            return super.transform(exp);
+        }
+    }
+
+    private static class MacroClassTransformingCodeVisitor extends TransformingCodeVisitor {
+
+        private final SourceUnit sourceUnit;
+
+        public MacroClassTransformingCodeVisitor(ClassCodeExpressionTransformer transformer, SourceUnit sourceUnit) {
+            super(transformer);
+            this.sourceUnit = sourceUnit;
+        }
+
+        @Override
+        public void visitConstructorCallExpression(final ConstructorCallExpression call) {
+            ClassNode type = call.getType();
+            if (type instanceof InnerClassNode) {
+                if (((InnerClassNode) type).isAnonymous() &&
+                        MACROCLASS_TYPE.getNameWithoutPackage().equals(type.getSuperClass().getNameWithoutPackage())) {
+                    try {
+                        String source = convertInnerClassToSource(type);
+
+                        MethodCallExpression macroCall = callX(
+                                propX(classX(ClassHelper.makeWithoutCaching(MacroBuilder.class, false)), "INSTANCE"),
+                                MACRO_METHOD,
+                                args(
+                                        constX(source),
+                                        MacroGroovyMethods.buildSubstitutionMap(sourceUnit, type),
+                                        classX(ClassHelper.make(ClassNode.class))
+                                )
+                        );
+
+                        macroCall.setSpreadSafe(false);
+                        macroCall.setSafe(false);
+                        macroCall.setImplicitThis(false);
+                        call.putNodeMetaData(MacroTransformation.class, macroCall);
+                        List<ClassNode> classes = sourceUnit.getAST().getClasses();
+                        for (Iterator<ClassNode> iterator = classes.iterator(); iterator.hasNext(); ) {
+                            final ClassNode aClass = iterator.next();
+                            if (aClass == type || type == aClass.getOuterClass()) {
+                                iterator.remove();
                             }
-                        } catch (Exception e) {
-                            // FIXME
-                            e.printStackTrace();
                         }
-                        return;
+                    } catch (Exception e) {
+                        // FIXME
+                        e.printStackTrace();
                     }
+                    return;
                 }
-                super.visitConstructorCallExpression(call);
-
             }
+            super.visitConstructorCallExpression(call);
 
-            private String convertInnerClassToSource(final ClassNode type) throws Exception {
-                String source = GeneralUtils.convertASTToSource(sourceUnit.getSource(), type);
-                // we need to remove the leading "{" and trailing "}"
-                source = source.substring(source.indexOf('{') + 1, source.lastIndexOf('}') - 1);
-                return source;
-            }
-        };
+        }
+
+        private String convertInnerClassToSource(final ClassNode type) throws Exception {
+            String source = GeneralUtils.convertASTToSource(sourceUnit.getSource(), type);
+            // we need to remove the leading "{" and trailing "}"
+            source = source.substring(source.indexOf('{') + 1, source.lastIndexOf('}') - 1);
+            return source;
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/30949043/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroMethodsCache.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroMethodsCache.java b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroMethodsCache.java
index d3e8ed4..90ca15b 100644
--- a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroMethodsCache.java
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroMethodsCache.java
@@ -34,6 +34,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
 /**
  * TODO share some code with {@link org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.ExtensionMethodCache}
  * @author Sergei Egorov <bs...@gmail.com>
+ * @since 2.5.0
  */
 class MacroMethodsCache {
 

http://git-wip-us.apache.org/repos/asf/groovy/blob/30949043/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroTransformation.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroTransformation.java b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroTransformation.java
index 983ba18..3ea441a 100644
--- a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroTransformation.java
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/transform/MacroTransformation.java
@@ -27,6 +27,7 @@ import org.codehaus.groovy.transform.GroovyASTTransformation;
 
 /**
  * @author Sergei Egorov <bs...@gmail.com>
+ * @since 2.5.0
  */
 
 @GroovyASTTransformation(phase = CompilePhase.CONVERSION)