IDEA-170804 Idea does't support @Newify annotation

This commit is contained in:
alexey.afanasiev
2017-05-26 17:23:43 +03:00
parent a1c4550763
commit ec6da70277
14 changed files with 469 additions and 133 deletions
@@ -1,53 +0,0 @@
/*
* Copyright 2000-2014 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 standardDsls
import com.intellij.psi.PsiClass
contributor(context(scope:annotatedScope(ctype: "groovy.lang.Newify"))) {
// For methods
def memb = enclosingMember()
if (memb) {
// For classes
def newifyName = "groovy.lang.Newify"
def annotated = memb.getAnnotations(newifyName)
def clazz = enclosingClass()
if (clazz) {
annotated += clazz.getAnnotations(newifyName)
}
for (a in annotated) {
def refs = a.findAttributeValue("value")
def auto = a.findAttributeValue("auto")
//For Python-like style
if (refs && !place.qualifier) {
for (c in refs.asList()) {
def targetClass = c.bind()
for (cons in (targetClass?.constructors ?: [])) {
method name: ((PsiClass) targetClass).name, params: cons.paramStringVector, type: targetClass.qualName
}
}
}
// For Ruby-like style
if (!auto || auto.eval()) {
for (cons in psiClass?.getConstructors()) {
method name: 'new', isStatic: true, params: cons.paramStringVector, type: psiClass?.qualName ?: "java.lang.Object"
}
}
}
}
}
@@ -109,6 +109,7 @@ public abstract class GroovyNamedArgumentProvider {
if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod)element;
method = PsiUtil.handleMirrorMethod(method);
PsiParameter[] parameters = method.getParameterList().getParameters();
if (!method.isConstructor() && !(parameters.length > 0 && canBeMap(parameters[0]))) continue;
@@ -32,7 +32,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
@@ -42,54 +41,43 @@ import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Sergey Evdokimov
*/
public class GroovyConstructorNamedArgumentProvider extends GroovyNamedArgumentProvider {
public abstract class GroovyConstructorNamedArgumentProvider extends GroovyNamedArgumentProvider {
private static final String METACLASS = "metaClass";
@NotNull
abstract List<PsiClass> getCorrespondingClasses(@NotNull GrCall call, @NotNull GroovyResolveResult resolveResult);
@Override
public void getNamedArguments(@NotNull GrCall call,
@NotNull GroovyResolveResult resolveResult,
@Nullable String argumentName,
boolean forCompletion,
@NotNull Map<String, NamedArgumentDescriptor> result) {
if (!(call instanceof GrNewExpression)) return;
PsiElement resolve = resolveResult.getElement();
if (resolve != null) {
if (!(resolve instanceof PsiMethod)) return;
PsiMethod method = (PsiMethod)resolve;
if (!method.isConstructor()) return;
}
GrNewExpression newCall = (GrNewExpression)call;
GrArgumentList argumentList = newCall.getArgumentList();
GrArgumentList argumentList = call.getArgumentList();
if (argumentList == null) return;
GrExpression[] expressionArguments = argumentList.getExpressionArguments();
if (expressionArguments.length > 1 || (expressionArguments.length == 1 && !(expressionArguments[0] instanceof GrReferenceExpression))) {
return;
}
for (GroovyResolveResult newResult : newCall.multiResolveClass()) {
PsiElement element = newResult.getElement();
if (!(element instanceof PsiClass)) continue;
for (PsiClass psiClass : getCorrespondingClasses(call, resolveResult)) {
if (!isClassHasConstructorWithMap(psiClass)) continue;
PsiClass aClass = (PsiClass)element;
if (!isClassHasConstructorWithMap(aClass)) continue;
PsiClassType classType = JavaPsiFacade.getElementFactory(aClass.getProject()).createType(aClass);
PsiClassType classType = JavaPsiFacade.getElementFactory(psiClass.getProject()).createType(psiClass);
processClass(call, classType, argumentName, result);
}
}
public static void processClass(@NotNull GrCall call,
PsiClassType type,
@Nullable String argumentName,
@@ -147,7 +135,7 @@ public class GroovyConstructorNamedArgumentProvider extends GroovyNamedArgumentP
}
}
private static boolean isClassHasConstructorWithMap(PsiClass aClass) {
public static boolean isClassHasConstructorWithMap(PsiClass aClass) {
PsiMethod[] constructors = aClass.getConstructors();
if (constructors.length == 0) return true;
@@ -197,7 +185,7 @@ public class GroovyConstructorNamedArgumentProvider extends GroovyNamedArgumentP
propertyName = ((PsiField)element).getName();
}
if (propertyName.equals(METACLASS)) return true;
if (METACLASS.equals(propertyName)) return true;
if (((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.STATIC)) return true;
@@ -0,0 +1,34 @@
/*
* Copyright 2000-2017 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
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMethod
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression
class GroovyNewExprNamedArgumentProvider : GroovyConstructorNamedArgumentProvider() {
internal override fun getCorrespondingClasses(call: GrCall, resolveResult: GroovyResolveResult): List<PsiClass> {
val newExpr = call as? GrNewExpression ?: return emptyList()
val resolve = resolveResult.element
(resolve as? PsiMethod)?.let {
if (!it.isConstructor) return emptyList()
}
return newExpr.multiResolveClass().mapNotNull { it.element as? PsiClass }
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2000-2017 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
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiMirrorElement
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall
class GroovyNewifyNamedArgumentProvider : GroovyConstructorNamedArgumentProvider() {
internal override fun getCorrespondingClasses(call: GrCall, resolveResult: GroovyResolveResult): List<PsiClass> {
val prototype = (resolveResult.element as? PsiMirrorElement)?.prototype as? PsiMethod ?: return emptyList()
if (!prototype.isConstructor) return emptyList()
return prototype.containingClass?.let { listOf(it) } ?: emptyList()
}
}
@@ -182,7 +182,7 @@ public class PsiUtil {
GrClosureSignature signature = GrClosureSignatureUtil.createSignature(method, substitutor, eraseParameterTypes);
//check for default constructor
if (method.isConstructor()) {
if (method.isConstructor() || handleMirrorMethod(method).isConstructor()) {
final PsiParameter[] parameters = method.getParameterList().getParameters();
if (parameters.length == 0 && argumentTypes.length == 1) {
return InheritanceUtil.isInheritor(argumentTypes[0], CommonClassNames.JAVA_UTIL_MAP)
@@ -212,6 +212,17 @@ public class PsiUtil {
return GrClosureSignatureUtil.ApplicabilityResult.inapplicable;
}
@NotNull
public static PsiMethod handleMirrorMethod(@NotNull PsiMethod method) {
if (method instanceof PsiMirrorElement) {
PsiElement prototype = ((PsiMirrorElement)method).getPrototype();
if (prototype instanceof PsiMethod) {
return (PsiMethod)prototype;
}
}
return method;
}
public static boolean isApplicable(@Nullable PsiType[] argumentTypes,
GrClosureType type,
GroovyPsiElement context) {
@@ -0,0 +1,99 @@
/*
* Copyright 2000-2017 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.noncode
import com.intellij.lang.java.JavaLanguage
import com.intellij.psi.*
import com.intellij.psi.impl.light.LightMethod
import com.intellij.psi.impl.light.LightModifierList
import com.intellij.psi.scope.PsiScopeProcessor
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil
import org.jetbrains.plugins.groovy.lang.psi.util.getParents
import org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil
internal val newifyAnnotationFqn = "groovy.lang.Newify"
class NewifyMemberContributor : NonCodeMembersContributor() {
override fun processDynamicElements(qualifierType: PsiType,
aClass: PsiClass?,
processor: PsiScopeProcessor,
place: PsiElement,
state: ResolveState) {
val qualifier = getQualifier(place)
for (annotation in listNewifyAnnotations(place)) {
val newifiedClasses = GrAnnotationUtil.getClassArrayValue(annotation, "value", true)
qualifier ?: newifiedClasses.flatMap { it.constructors.asList() }.forEach {
ResolveUtil.processElement(processor, NewifiedConstructor(it, "by @Newify", it.name, true, arrayOf(PsiModifier.STATIC)), state)
}
val createNewMethods = GrAnnotationUtil.inferBooleanAttributeNotNull(annotation, "auto")
val type = (qualifier as? GrReferenceExpression)?.resolve() as? PsiClass
if (type != null && createNewMethods) {
type.constructors.forEach {
ResolveUtil.processElement(processor, NewifiedConstructor(it, "by @Newify", "new", false, arrayOf(PsiModifier.STATIC)), state)
}
}
}
}
fun listNewifyAnnotations(place: PsiElement): List<PsiAnnotation> {
return place.getParents().map { it.second }.flatMap {
val owner = it as? PsiModifierListOwner ?: return@flatMap emptySequence<PsiAnnotation>()
val seq = owner.modifierList?.annotations?.asSequence()?.filter { it.qualifiedName == newifyAnnotationFqn }
return@flatMap seq ?: emptySequence<PsiAnnotation>()
}.toList()
}
fun getQualifier(elem: PsiElement): PsiElement? {
return (elem as? GrReferenceExpression)?.qualifierExpression
}
class NewifiedConstructor(val myPrototype: PsiMethod, val myOriginInfo: String, val newName: String, val asConstructor: Boolean, val modifiers:Array<String>)
: LightMethod(myPrototype.manager, myPrototype, myPrototype.containingClass!!), OriginInfoAwareElement, PsiMirrorElement {
override fun getPrototype(): PsiElement {
return myPrototype
}
val myModifierList: LightModifierList = LightModifierList(myPrototype.manager, JavaLanguage.INSTANCE, *modifiers)
override fun getName(): String {
return newName
}
override fun getOriginInfo(): String {
return myOriginInfo
}
override fun hasModifierProperty(name: String): Boolean {
return myModifierList.hasModifierProperty(name)
}
override fun getModifierList(): PsiModifierList {
return myModifierList
}
override fun isConstructor(): Boolean {
return asConstructor
}
}
}
+3 -1
View File
@@ -99,6 +99,7 @@
<membersContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.noncode.GrCollectionTypeMembersProvider"/>
<membersContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.noncode.MixinMemberContributor"/>
<membersContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.noncode.NewifyMemberContributor"/>
<membersContributor implementation="org.jetbrains.plugins.groovy.swingBuilder.SwingBuilderNonCodeMemberContributor"/>
<membersContributor implementation="org.jetbrains.plugins.groovy.dgm.DGMMemberContributor"/>
@@ -178,7 +179,8 @@
<scriptTypeDetector implementation="org.jetbrains.plugins.groovy.gant.GantScriptTypeDetector"/>
<namedArgumentProvider implementation="org.jetbrains.plugins.groovy.lang.GroovySourceCodeNamedArgumentProvider" order="last"/>
<namedArgumentProvider implementation="org.jetbrains.plugins.groovy.lang.GroovyConstructorNamedArgumentProvider"/>
<namedArgumentProvider implementation="org.jetbrains.plugins.groovy.lang.GroovyNewifyNamedArgumentProvider"/>
<namedArgumentProvider implementation="org.jetbrains.plugins.groovy.lang.GroovyNewExprNamedArgumentProvider"/>
<namedArgumentProvider implementation="org.jetbrains.plugins.groovy.lang.GroovyMethodReturnNamedArgumentProvider" order="last"/>
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -61,15 +61,4 @@ class GroovyTransformationsTest extends LightCodeInsightFixtureTestCase {
void testBindableTransform() throws Throwable { doPlainTest() }
void testVetoableTransform() throws Throwable { doPlainTest() }
void testNewifyTransform1() throws Throwable {
myFixture.configureByFile(getTestName(false) + ".groovy")
myFixture.completeBasic()
assert myFixture.lookupElementStrings.containsAll(['newInstance', 'new', 'new', 'newInstance'])
}
void testNewifyTransform2() throws Throwable { doVariantsTest('Leaf', 'Leaf', 'Leaf', 'Boolean') }
void testNewifyTransform3() throws Throwable { doVariantsTest('Bazz', 'Bazz') }
}
@@ -0,0 +1,171 @@
/*
* Copyright 2000-2017 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
import com.intellij.testFramework.LightProjectDescriptor
import groovy.transform.CompileStatic
import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor
import org.jetbrains.plugins.groovy.codeInspection.assignment.GroovyAssignabilityCheckInspection
import org.jetbrains.plugins.groovy.codeInspection.untypedUnresolvedAccess.GrUnresolvedAccessInspection
import org.jetbrains.plugins.groovy.lang.highlighting.GrHighlightingTestBase
@CompileStatic
class NewifySupportTest extends GrHighlightingTestBase {
final LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST
@Override
void setUp() throws Exception {
super.setUp()
fixture.enableInspections(GrUnresolvedAccessInspection, GroovyAssignabilityCheckInspection)
fixture.addClass('''
public class A {
String name;
int age;
public A(){}
public A(String name){}
}
''')
}
void testAutoNewify() {
testHighlighting """
@Newify
class B {
def a = A.new()
def i = Integer.new(1)
}
"""
testHighlighting """
@Newify
class B {
def a = A.new("B")
}
"""
testHighlighting """
@Newify
class B {
def a = A.new(name :"bar")
}
"""
testHighlighting """
class B {
@Newify
def a = A.new(name :"bar")
}
"""
testHighlighting """
class B {
@Newify
def a (){ return A.new(name :"bar")}
}
"""
testHighlighting """
class B {
@Newify(value = A, auto = false)
def a (){ return A.<warning>new</warning>()}
}
"""
}
void testNewifyByClass() {
testHighlighting """
@Newify([A, Integer])
class B {
def a = A()
def i = Integer(1)
}
"""
testHighlighting """
@Newify(A)
class B {
def a = A("B")
}
"""
testHighlighting """
@Newify(A)
class B {
def a = A(name :"bar")
}
"""
testHighlighting """
class B {
@Newify(A)
def a = A(name :"bar")
}
"""
testHighlighting """
class B {
@Newify(A)
def a (){ return A(name :"bar")}
}
"""
testHighlighting """
class B {
@Newify
def a (){ return A<warning>()</warning>}
}
"""
}
void testNewifyMapLookup() {
testHighlighting """
@Newify(A)
class B {
def a = A(<caret>)
}
"""
fixture.completeBasic()
fixture.lookupElementStrings.with {
assert contains("name")
assert contains("age")
}
}
void testNewifyAutoLookup() {
fixture.configureByText 'a.groovy', """
@Newify(A)
class B {
def a = A.<caret>
}
"""
fixture.completeBasic()
fixture.lookupElementStrings.with {
assert contains("new")
}
}
void testNewifyAutoMapLookup() {
testHighlighting """
@Newify(A)
class B {
def a = A.new(<caret>)
}
"""
fixture.completeBasic()
fixture.lookupElementStrings.with {
assert contains("name")
assert contains("age")
}
}
}
@@ -827,6 +827,111 @@ def test() {
assertNotNull(ref.resolve())
}
void testAutoNewify() {
fixture.addClass('''
class A {
String name;
public A(){}
}
''')
def resolved = configureByText("""
@Newify
class B {
def a = A.ne<caret>w()
}
""").resolve()
assertInstanceOf(resolved, PsiMethod)
resolved = configureByText("""
@Newify
class B {
def a = A.ne<caret>w(name :"bar")
}
""").resolve()
assertInstanceOf(resolved, PsiMethod)
resolved = configureByText("""
class B {
@Newify
def a = A.ne<caret>w(name :"bar")
}
""").resolve()
assertInstanceOf(resolved, PsiMethod)
resolved = configureByText("""
class B {
@Newify
def a (){ return A.ne<caret>w(name :"bar")}
}
""").resolve()
assertInstanceOf(resolved, PsiMethod)
def ref = configureByText("""
class B {
@Newify(auto = false)
def a (){ return A.ne<caret>w()}
}
""")
assert ref.resolve() == null
}
void testNewifyByClass() {
fixture.addClass('''
class Aa {
String name;
public Aa(){}
}
''')
def resolved = configureByText("""
@Newify(Aa)
class B {
def a = A<caret>a()
}
""").resolve()
assertInstanceOf(resolved, PsiMethod)
resolved = configureByText("""
@Newify(Aa)
class B {
def a = A<caret>a(name :"bar")
}
""").resolve()
assertInstanceOf(resolved, PsiMethod)
resolved = configureByText("""
class B {
@Newify(Aa)
def a = A<caret>a(name :"bar")
}
""").resolve()
assertInstanceOf(resolved, PsiMethod)
resolved = configureByText("""
class B {
@Newify(Aa)
def a (){ return A<caret>a(name :"bar")}
}
""").resolve()
assertInstanceOf(resolved, PsiMethod)
resolved = configureByText("""
class B {
@Newify
def a (){ return A<caret>a(name :"bar")}
}
""").resolve()
assertInstanceOf(resolved, PsiClass)
}
void testStringInjectionDontOverrideItParameter() {
def ref = configureByText("""
[2, 3, 4].collect {"\${it.toBigDeci<caret>mal()}"}
@@ -1,3 +0,0 @@
@Newify rubyLikeNew() {
assert Integer.ne<caret> == 42
}
@@ -1,13 +0,0 @@
class Tree {
Tree(Tree l, Tree r) { }
}
class Leaf extends Tree {
Leaf(int value) {}
Leaf(String value) {}
}
@Newify([Tree, Leaf])
buildTree() {
Tree(Tree(Leaf(1), Leaf(2)), Lea<caret>)
}
@@ -1,25 +0,0 @@
class Foo {
Foo(String st){}
}
class Bazz {
Bazz(String st){}
}
@Newify(auto=false, value=Foo)
class Main {
@Newify() // turn auto on for field
def field1 = java.math.BigInteger.new(42)
def field2, field3, field4
@Newify(Bar)
def process() {
field2 = Bar("my bar")
}
@Newify(Bazz)
Main() {
field3 = Foo("my foo")
field4 = Baz<caret>()
}
}