From 9a187b0213993832fa10df61890a0084e0c2d7f4 Mon Sep 17 00:00:00 2001 From: Daniil Ovchinnikov Date: Fri, 22 Mar 2019 17:21:21 +0300 Subject: [PATCH] [gradle] process conventions' members as additional members of Project receiver This fixes `project.someProperty` cases where `someProperty` is a property of some convention. Also conventions doesn't support setter invocation via method call, so they are ignored. --- .../META-INF/gradle-groovy-integration.xml | 1 + .../resolve/GradleConventionsContributor.kt | 31 ++++++++++ .../service/resolve/GradleJavaContributor.kt | 19 +----- .../GradleNonCodeMembersContributor.kt | 4 +- .../gradle/java/src/service/resolve/util.kt | 13 ++-- .../dsl/GradleConventionsTest.groovy | 61 +++++++++++++++++++ .../plugins/groovy/util/ResolveTest.java | 26 +++++++- 7 files changed, 133 insertions(+), 22 deletions(-) create mode 100644 plugins/gradle/java/src/service/resolve/GradleConventionsContributor.kt create mode 100644 plugins/gradle/java/testSources/dsl/GradleConventionsTest.groovy diff --git a/plugins/gradle/java/resources/META-INF/gradle-groovy-integration.xml b/plugins/gradle/java/resources/META-INF/gradle-groovy-integration.xml index 70552ce81575..37fd60052ca9 100644 --- a/plugins/gradle/java/resources/META-INF/gradle-groovy-integration.xml +++ b/plugins/gradle/java/resources/META-INF/gradle-groovy-integration.xml @@ -62,6 +62,7 @@ + diff --git a/plugins/gradle/java/src/service/resolve/GradleConventionsContributor.kt b/plugins/gradle/java/src/service/resolve/GradleConventionsContributor.kt new file mode 100644 index 000000000000..124daf13646c --- /dev/null +++ b/plugins/gradle/java/src/service/resolve/GradleConventionsContributor.kt @@ -0,0 +1,31 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.plugins.gradle.service.resolve + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiType +import com.intellij.psi.ResolveState +import com.intellij.psi.scope.PsiScopeProcessor +import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.* +import org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor + +class GradleConventionsContributor : NonCodeMembersContributor() { + + companion object { + val conventions = arrayOf( + GRADLE_API_BASE_PLUGIN_CONVENTION, + GRADLE_API_JAVA_PLUGIN_CONVENTION, + GRADLE_API_APPLICATION_PLUGIN_CONVENTION, + GRADLE_API_WAR_CONVENTION + ) + } + + /** + * Plugin conventions are available on the project instance, be it top level reference or `project.something`. + */ + override fun getParentClassName(): String = GRADLE_API_PROJECT + + override fun processDynamicElements(qualifierType: PsiType, processor: PsiScopeProcessor, place: PsiElement, state: ResolveState) { + // TODO process conventions from Gradle import instead of hardcoded ones + processDelegatedDeclarations(processor, state, place, *conventions) + } +} diff --git a/plugins/gradle/java/src/service/resolve/GradleJavaContributor.kt b/plugins/gradle/java/src/service/resolve/GradleJavaContributor.kt index 983584e33241..7706b1dadd5f 100644 --- a/plugins/gradle/java/src/service/resolve/GradleJavaContributor.kt +++ b/plugins/gradle/java/src/service/resolve/GradleJavaContributor.kt @@ -1,12 +1,10 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.gradle.service.resolve import com.intellij.patterns.PsiJavaElementPattern import com.intellij.patterns.PsiJavaPatterns.psiElement import com.intellij.patterns.StandardPatterns.or import com.intellij.psi.PsiElement -import com.intellij.psi.ResolveState -import com.intellij.psi.scope.PsiScopeProcessor import groovy.lang.Closure import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.* import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock @@ -20,6 +18,7 @@ import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.DelegatesToInfo * @author Vladislav.Soroka */ class GradleJavaContributor : GradleMethodContextContributor { + companion object { val sourceSetsClosure: GroovyClosurePattern = groovyClosure().inMethod(psiMethod(GRADLE_API_JAVA_PLUGIN_CONVENTION, "sourceSets")) val sourceDirectorySetClosure: PsiJavaElementPattern.Capture = psiElement().andOr( @@ -36,16 +35,4 @@ class GradleJavaContributor : GradleMethodContextContributor { } return null } - - override fun process(methodCallInfo: List, processor: PsiScopeProcessor, state: ResolveState, place: PsiElement): Boolean { - if (!GradleResolverUtil.processDeclarations(processor, state, place, - GRADLE_API_BASE_PLUGIN_CONVENTION, - GRADLE_API_JAVA_PLUGIN_CONVENTION, - GRADLE_API_APPLICATION_PLUGIN_CONVENTION, - GRADLE_API_WAR_CONVENTION)) { - return false - } - - return true - } -} \ No newline at end of file +} diff --git a/plugins/gradle/java/src/service/resolve/GradleNonCodeMembersContributor.kt b/plugins/gradle/java/src/service/resolve/GradleNonCodeMembersContributor.kt index bf2a2aeca0e9..c72ec8713b4f 100644 --- a/plugins/gradle/java/src/service/resolve/GradleNonCodeMembersContributor.kt +++ b/plugins/gradle/java/src/service/resolve/GradleNonCodeMembersContributor.kt @@ -96,7 +96,9 @@ class GradleNonCodeMembersContributor : NonCodeMembersContributor() { extensionsData.findProperty(propCandidate)?.let(processVariable) } else { - processDeclarations(aClass, processor, state, place) + if (aClass.qualifiedName !in GradleConventionsContributor.conventions) { + processDeclarations(aClass, processor, state, place) + } val propCandidate = place.references.singleOrNull()?.canonicalText ?: return val domainObjectType = (qualifierType.superTypes.firstOrNull { it is PsiClassType } as? PsiClassType)?.parameters?.singleOrNull() ?: return diff --git a/plugins/gradle/java/src/service/resolve/util.kt b/plugins/gradle/java/src/service/resolve/util.kt index a670fafe2396..ff74a0e92364 100644 --- a/plugins/gradle/java/src/service/resolve/util.kt +++ b/plugins/gradle/java/src/service/resolve/util.kt @@ -37,11 +37,16 @@ internal fun PsiFile?.isGradleScript() = this?.originalFile?.virtualFile?.extens val RESOLVED_CODE: Key = Key.create("gradle.resolved") // TODO extract API for delegation -fun processDelegatedDeclarations(processor: PsiScopeProcessor, state: ResolveState, place: PsiElement, fqn: String): Boolean { +fun processDelegatedDeclarations(processor: PsiScopeProcessor, state: ResolveState, place: PsiElement, vararg fqns: String): Boolean { val javaPsiFacade = JavaPsiFacade.getInstance(place.project) - val clazz = javaPsiFacade.findClass(fqn, place.resolveScope) ?: return true - val type = javaPsiFacade.elementFactory.createType(clazz) - return type.processReceiverType(processor, state, place) + for (fqn in fqns) { + val clazz = javaPsiFacade.findClass(fqn, place.resolveScope) ?: continue + val type = javaPsiFacade.elementFactory.createType(clazz) + if (!type.processReceiverType(processor, state, place)) { + return false + } + } + return true } fun processDeclarations(aClass: PsiClass, diff --git a/plugins/gradle/java/testSources/dsl/GradleConventionsTest.groovy b/plugins/gradle/java/testSources/dsl/GradleConventionsTest.groovy new file mode 100644 index 000000000000..f3efbff4aba6 --- /dev/null +++ b/plugins/gradle/java/testSources/dsl/GradleConventionsTest.groovy @@ -0,0 +1,61 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.plugins.gradle.dsl + +import com.intellij.psi.PsiMethod +import com.intellij.testFramework.RunAll +import groovy.transform.CompileStatic +import org.jetbrains.plugins.gradle.highlighting.GradleHighlightingBaseTest +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall +import org.jetbrains.plugins.groovy.util.ResolveTest +import org.junit.Test + +import static org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_JAVA_PLUGIN_CONVENTION + +@CompileStatic +class GradleConventionsTest extends GradleHighlightingBaseTest implements ResolveTest { + + // The test should fail when conventions would be taken from Gradle import + // instead of being processed in org.jetbrains.plugins.gradle.service.resolve.GradleConventionsContributor unconditionally. + // In such case `apply plugin: 'java'` is required to fix the test. + @Test + void test() { + importProject("") + new RunAll().append { + 'property read'() + } append { + 'property read via project'() + } append { + 'property write'() + } append { + 'setter method'() + } run() + } + + void 'property read'() { + doTest('docsDir') { + methodTest(resolveTest(PsiMethod), 'getDocsDir', GRADLE_API_JAVA_PLUGIN_CONVENTION) + } + } + + void 'property read via project'() { + doTest('project.docsDir') { + methodTest(resolveTest(PsiMethod), 'getDocsDir', GRADLE_API_JAVA_PLUGIN_CONVENTION) + } + } + + void 'property write'() { + doTest('sourceCompatibility = 42') { + methodTest(resolveTest(PsiMethod), 'setSourceCompatibility', GRADLE_API_JAVA_PLUGIN_CONVENTION) + } + } + + void 'setter method'() { + doTest('targetCompatibility("1.8")') { + def call = elementUnderCaret(GrMethodCall) + def result = call.advancedResolve() + assert result.invokedOnProperty + // getTargetCompatibility() is resolved, just because it exists, but later it's highlighted with warning + methodTest(assertInstanceOf(result.element, PsiMethod), 'getTargetCompatibility', GRADLE_API_JAVA_PLUGIN_CONVENTION) + } + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/util/ResolveTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/util/ResolveTest.java index 006abad204ce..f27f440a14b2 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/util/ResolveTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/util/ResolveTest.java @@ -1,7 +1,9 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.util; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiReference; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -20,7 +22,13 @@ public interface ResolveTest extends BaseTest { @NotNull default T referenceByText(@NotNull String text, @NotNull Class refType) { - PsiReference ref = configureByText(text).findReferenceAt(getFixture().getCaretOffset()); + configureByText(text); + return referenceUnderCaret(refType); + } + + @NotNull + default T referenceUnderCaret(@NotNull Class refType) { + PsiReference ref = getGroovyFile().findReferenceAt(getFixture().getCaretOffset()); return assertInstanceOf(ref, refType); } @@ -43,6 +51,10 @@ public interface ResolveTest extends BaseTest { return resolveTest(referenceByText(text), clazz); } + default T resolveTest(@Nullable Class clazz) { + return resolveTest(referenceUnderCaret(GroovyReference.class), clazz); + } + default T resolveTest(@NotNull GroovyReference reference, @Nullable Class clazz) { Collection results = reference.resolve(false); if (clazz == null) { @@ -62,4 +74,16 @@ public interface ResolveTest extends BaseTest { assertType(fqn, delegatesToInfo.getTypeToDelegate()); assertEquals(strategy, delegatesToInfo.getStrategy()); } + + default void methodTest(@NotNull PsiMethod method, String name, String fqn) { + assertEquals(name, method.getName()); + PsiClass containingClass = method.getContainingClass(); + if (fqn == null) { + assertNull(containingClass); + } + else { + assertNotNull(containingClass); + assertEquals(fqn, containingClass.getQualifiedName()); + } + } }