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 extends GroovyResolveResult> 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());
+ }
+ }
}