[groovy] support AutoClone copy constructor and simple styles (IDEA-134714)

This commit is contained in:
Daniil Ovchinnikov
2016-10-11 11:39:16 +03:00
parent 41c2429966
commit b08aa2994d
9 changed files with 204 additions and 58 deletions
@@ -93,7 +93,10 @@ import org.jetbrains.plugins.groovy.lang.psi.stubs.index.*;
import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.*;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyConstantExpressionEvaluator;
import org.jetbrains.plugins.groovy.lang.resolve.*;
import org.jetbrains.plugins.groovy.lang.resolve.ast.*;
import org.jetbrains.plugins.groovy.lang.resolve.ast.AutoExternalizeContributor;
import org.jetbrains.plugins.groovy.lang.resolve.ast.ConstructorAnnotationsProcessor;
import org.jetbrains.plugins.groovy.lang.resolve.ast.InheritConstructorContributor;
import org.jetbrains.plugins.groovy.lang.resolve.ast.LoggingContributor;
import org.jetbrains.plugins.groovy.lang.resolve.ast.builder.strategy.DefaultBuilderStrategySupport;
import org.jetbrains.plugins.groovy.lang.resolve.ast.builder.strategy.ExternalBuilderStrategySupport;
import org.jetbrains.plugins.groovy.lang.resolve.ast.builder.strategy.InitializerBuilderStrategySupport;
@@ -108,6 +111,7 @@ import org.jetbrains.plugins.groovy.swingBuilder.SwingBuilderNamedArgumentProvid
import org.jetbrains.plugins.groovy.swingBuilder.SwingBuilderNonCodeMemberContributor;
import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport;
import org.jetbrains.plugins.groovy.transformations.impl.*;
import org.jetbrains.plugins.groovy.transformations.impl.autoClone.AutoCloneTransformationSupport;
/**
* Upsource
@@ -143,7 +147,7 @@ public class GroovyCoreEnvironment {
CoreApplicationEnvironment.registerExtensionPoint(Extensions.getRootArea(), AstTransformationSupport.EP_NAME, AstTransformationSupport.class);
appEnvironment.addExtension(AstTransformationSupport.EP_NAME, new AutoExternalizeContributor());
appEnvironment.addExtension(AstTransformationSupport.EP_NAME, new AutoCloneContributor());
appEnvironment.addExtension(AstTransformationSupport.EP_NAME, new AutoCloneTransformationSupport());
appEnvironment.addExtension(AstTransformationSupport.EP_NAME, new ConstructorAnnotationsProcessor());
appEnvironment.addExtension(AstTransformationSupport.EP_NAME, new InheritConstructorContributor());
appEnvironment.addExtension(AstTransformationSupport.EP_NAME, new DefaultBuilderStrategySupport());
@@ -46,7 +46,6 @@ public final class GroovyCommonClassNames {
public static final String GROOVY_LANG_REFERENCE = "groovy.lang.Reference";
public static final String JAVA_UTIL_LINKED_HASH_MAP = "java.util.LinkedHashMap";
public static final String GROOVY_TRANSFORM_AUTO_EXTERNALIZE = "groovy.transform.AutoExternalize";
public static final String GROOVY_TRANSFORM_AUTO_CLONE = "groovy.transform.AutoClone";
public static final String GROOVY_LANG_CATEGORY = "groovy.lang.Category";
public static final String GROOVY_LANG_MIXIN = "groovy.lang.Mixin";
public static final String GROOVY_UTIL_TEST_CASE = "groovy.util.GroovyTestCase";
@@ -1,40 +0,0 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed 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.jetbrains.plugins.groovy.lang.resolve.ast;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.impl.light.LightMethodBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport;
import org.jetbrains.plugins.groovy.transformations.TransformationContext;
/**
* @author Max Medvedev
*/
public class AutoCloneContributor implements AstTransformationSupport {
@Override
public void applyTransformation(@NotNull TransformationContext context) {
if (!context.hasAnnotation(GroovyCommonClassNames.GROOVY_TRANSFORM_AUTO_CLONE)) return;
final LightMethodBuilder clone = new LightMethodBuilder(context.getManager(), "clone");
clone.addModifier(PsiModifier.PUBLIC);
clone.addException(CloneNotSupportedException.class.getName());
clone.setOriginInfo("created by @AutoClone");
context.addMethod(clone);
}
}
@@ -59,3 +59,5 @@ fun isUnderTransformation(clazz: PsiClass?): Boolean {
false
}
}
infix operator fun TransformationContext.plusAssign(method: PsiMethod) = addMethod(method)
@@ -0,0 +1,87 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed 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.jetbrains.plugins.groovy.transformations.impl.autoClone
import com.intellij.psi.CommonClassNames
import com.intellij.psi.PsiEnumConstant
import com.intellij.psi.PsiModifier
import com.intellij.psi.PsiType
import com.intellij.psi.impl.light.LightMethodBuilder
import org.jetbrains.plugins.groovy.GroovyLanguage
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil
import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport
import org.jetbrains.plugins.groovy.transformations.TransformationContext
import org.jetbrains.plugins.groovy.transformations.plusAssign
class AutoCloneTransformationSupport : AstTransformationSupport {
private companion object {
val AUTO_CLONE_FQN = "groovy.transform.AutoClone"
val ORIGIN_INFO = "created by @AutoClone"
val CNSE_FQN = "java.lang.CloneNotSupportedException"
}
override fun applyTransformation(context: TransformationContext) {
val annotation = context.getAnnotation(AUTO_CLONE_FQN) ?: return
context.addInterface(CommonClassNames.JAVA_LANG_CLONEABLE)
// public T clone() throws CloneNotSupportedException
context += LightMethodBuilder(context.manager, "clone").apply {
addModifier(PsiModifier.PUBLIC)
setMethodReturnType(TypesUtil.createType(context.codeClass))
addException(CNSE_FQN)
navigationElement = annotation
originInfo = ORIGIN_INFO
}
val value = annotation.findDeclaredAttributeValue("style") as? GrReferenceExpression ?: return
val constant = value.resolve() as? PsiEnumConstant ?: return
if (constant.containingClass?.qualifiedName != "groovy.transform.AutoCloneStyle") return
when (constant.name) {
"COPY_CONSTRUCTOR" -> {
if (context.codeClass.codeConstructors.size == 0) {
context += LightMethodBuilder(context.codeClass, GroovyLanguage).apply {
isConstructor = true
addModifier(PsiModifier.PUBLIC)
navigationElement = context.codeClass
}
}
// protected T(T other)
context += LightMethodBuilder(context.codeClass, GroovyLanguage).apply {
isConstructor = true
addModifier(PsiModifier.PROTECTED)
addParameter("other", TypesUtil.createType(context.codeClass))
navigationElement = context.codeClass
originInfo = ORIGIN_INFO
}
}
"SIMPLE" -> {
// protected void cloneOrCopyMembers(T other) throws CloneNotSupportedException
context += LightMethodBuilder(context.manager, "cloneOrCopyMembers").apply {
addModifier(PsiModifier.PROTECTED)
setMethodReturnType(PsiType.VOID)
addParameter("other", TypesUtil.createType(context.codeClass))
addException(CNSE_FQN)
navigationElement = annotation
originInfo = ORIGIN_INFO
}
}
}
}
}
+1 -1
View File
@@ -118,7 +118,7 @@
<closureMissingMethodContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.PluginXmlClosureMemberContributor"/>
<astTransformationSupport implementation="org.jetbrains.plugins.groovy.lang.resolve.ast.AutoExternalizeContributor"/>
<astTransformationSupport implementation="org.jetbrains.plugins.groovy.lang.resolve.ast.AutoCloneContributor"/>
<astTransformationSupport implementation="org.jetbrains.plugins.groovy.transformations.impl.autoClone.AutoCloneTransformationSupport"/>
<astTransformationSupport implementation="org.jetbrains.plugins.groovy.lang.resolve.ast.ConstructorAnnotationsProcessor"/>
<astTransformationSupport implementation="org.jetbrains.plugins.groovy.lang.resolve.ast.InheritConstructorContributor"/>
<astTransformationSupport implementation="org.jetbrains.plugins.groovy.lang.resolve.ast.builder.strategy.DefaultBuilderStrategySupport"/>
@@ -730,12 +730,6 @@ class Foo {
assertNotNull(resolve('a.groovy'))
}
void testAutoClone() {
def element = resolve('a.groovy', PsiMethod)
assertTrue element.containingClass.name == 'Foo'
assertSize 1, element.throwsList.referencedTypes
}
void testDontUseQualifierScopeInDGM() {
assertNull resolve('a.groovy')
}
@@ -0,0 +1,108 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed 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.jetbrains.plugins.groovy.transformations
import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor
import org.jetbrains.plugins.groovy.LightGroovyTestCase
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
class GrAutoCloneTransformationSupportTest extends LightGroovyTestCase {
GroovyLightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST
void 'test clone() return type'() {
doExpressionTypeTest '''\
@groovy.transform.AutoClone
class A1 {}
new A1().clo<caret>ne()
''', 'A1'
}
void 'test clone() return type overridden'() {
doExpressionTypeTest '''\
@groovy.transform.AutoClone
class A1 {}
@groovy.transform.AutoClone
class A2 extends A1 {}
new A2().clo<caret>ne()
''', 'A2'
}
void 'test clone() usage from java'() {
myFixture.with {
addFileToProject 'Pogo.groovy', '''\
@groovy.transform.AutoClone
class Pogo {}
'''
configureByText 'Main.java', '''\
class Main {
void foo() {
<error descr="Unhandled exception: java.lang.CloneNotSupportedException">new Pogo().clone();</error>;
try {
Pogo pogo = new Pogo().clone();
} catch (java.lang.CloneNotSupportedException e) {}
}
}
'''
checkHighlighting()
}
}
void 'test copy constructor usage from java'() {
myFixture.with {
addFileToProject 'Pogo.groovy', '''\
@groovy.transform.AutoClone(style=groovy.transform.AutoCloneStyle.COPY_CONSTRUCTOR)
class Pogo {}
'''
configureByText 'Main.java', '''\
class Main {
void foo() {
Pogo a = new Pogo();
Pogo b = new Pogo(a);
}
}
'''
checkHighlighting()
}
}
void 'test cloneOrCopyMembers() usage from java'() {
myFixture.with {
addFileToProject 'Pogo.groovy', '''\
@groovy.transform.AutoClone(style=groovy.transform.AutoCloneStyle.SIMPLE)
class Pogo {}
'''
configureByText 'Pojo.java', '''\
class Pojo extends Pogo {
void foo() {
Pogo pogo = new Pogo();
<error descr="Unhandled exception: java.lang.CloneNotSupportedException">cloneOrCopyMembers(pogo);</error>
cloneOrCopyMembers<error descr="'cloneOrCopyMembers(Pogo)' in '' cannot be applied to '()'">()</error>;
}
}
'''
checkHighlighting()
}
}
private void doExpressionTypeTest(String text, String expectedType) {
def file = myFixture.configureByText('_.groovy', text)
def ref = file.findReferenceAt(myFixture.editor.caretModel.offset) as GrReferenceExpression
def actual = ref.type
assertType(expectedType, actual)
}
}
@@ -1,8 +0,0 @@
import groovy.transform.AutoClone
@AutoClone
class Foo {
def foo
}
print new Foo().clo<ref>ne()