From fc10708ccda66aa11b62c8aeee11910e9ac33ffd Mon Sep 17 00:00:00 2001 From: Daniil Ovchinnikov Date: Mon, 25 Mar 2019 20:22:07 +0300 Subject: [PATCH] [gradle] resolve ArtifactHandler methods using imported configurations data --- .../META-INF/gradle-groovy-integration.xml | 1 + .../GradleArtifactHandlerContributor.kt | 63 ++++++++++++++ .../resolve/GradleArtifactsContributor.kt | 61 +------------- .../GradleSetterAsMethodContributor.kt | 4 +- .../dsl/GradleArtifactsTest.groovy | 82 +++++++++++++++++-- .../resolve/GradleCommonClassNames.java | 1 + .../impl/synthetic/GrLightMethodBuilder.java | 7 +- 7 files changed, 151 insertions(+), 68 deletions(-) create mode 100644 plugins/gradle/java/src/service/resolve/GradleArtifactHandlerContributor.kt 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 37fd60052ca9..1b23735b4f2e 100644 --- a/plugins/gradle/java/resources/META-INF/gradle-groovy-integration.xml +++ b/plugins/gradle/java/resources/META-INF/gradle-groovy-integration.xml @@ -63,6 +63,7 @@ + diff --git a/plugins/gradle/java/src/service/resolve/GradleArtifactHandlerContributor.kt b/plugins/gradle/java/src/service/resolve/GradleArtifactHandlerContributor.kt new file mode 100644 index 000000000000..e7dcd306a1d3 --- /dev/null +++ b/plugins/gradle/java/src/service/resolve/GradleArtifactHandlerContributor.kt @@ -0,0 +1,63 @@ +// 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.* +import com.intellij.psi.scope.PsiScopeProcessor +import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_ARTIFACT_HANDLER +import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_CONFIGURABLE_PUBLISH_ARTIFACT +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil.createType +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil.getJavaLangObject +import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder +import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames.GROOVY_LANG_CLOSURE +import org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor +import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.DELEGATES_TO_KEY +import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.DELEGATES_TO_STRATEGY_KEY +import org.jetbrains.plugins.groovy.lang.resolve.getName +import org.jetbrains.plugins.groovy.lang.resolve.shouldProcessMethods + +class GradleArtifactHandlerContributor : NonCodeMembersContributor() { + + override fun getParentClassName(): String? = GRADLE_API_ARTIFACT_HANDLER + + override fun processDynamicElements(qualifierType: PsiType, + clazz: PsiClass?, + processor: PsiScopeProcessor, + place: PsiElement, + state: ResolveState) { + if (clazz == null) return + if (!processor.shouldProcessMethods()) return + + val data = GradleExtensionsContributor.getExtensionsFor(place) ?: return + val methodName = processor.getName(state) + val manager = place.manager + val objectType = getJavaLangObject(place) + val objectVarargType = PsiEllipsisType(objectType) + val closureType = createType(GROOVY_LANG_CLOSURE, place) + val configurableArtifactType = createType(GRADLE_API_CONFIGURABLE_PUBLISH_ARTIFACT, place) + + // TODO store configurations in a map + for (configuration in data.configurations) { + val configurationName = configuration.name ?: continue + if (methodName != null && configurationName != methodName) continue + + val method1 = GrLightMethodBuilder(manager, configurationName).apply { + containingClass = clazz + returnType = configurableArtifactType + addParameter("artifactNotation", objectType) + addAndGetParameter("configureClosure", closureType).apply { + putUserData(DELEGATES_TO_KEY, GRADLE_API_CONFIGURABLE_PUBLISH_ARTIFACT) + putUserData(DELEGATES_TO_STRATEGY_KEY, 1) + } + } + if (!processor.execute(method1, state)) return + + val method2 = GrLightMethodBuilder(manager, configurationName).apply { + containingClass = clazz + returnType = PsiType.NULL + addParameter("artifactNotation", objectType) + addParameter("artifactNotations", objectVarargType) + } + if (!processor.execute(method2, state)) return + } + } +} diff --git a/plugins/gradle/java/src/service/resolve/GradleArtifactsContributor.kt b/plugins/gradle/java/src/service/resolve/GradleArtifactsContributor.kt index 1eeeae627068..625fea3802fb 100644 --- a/plugins/gradle/java/src/service/resolve/GradleArtifactsContributor.kt +++ b/plugins/gradle/java/src/service/resolve/GradleArtifactsContributor.kt @@ -1,28 +1,14 @@ // 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.PsiJavaPatterns.psiElement -import com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT -import com.intellij.psi.JavaPsiFacade -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiType -import com.intellij.psi.ResolveState -import com.intellij.psi.scope.PsiScopeProcessor -import com.intellij.psi.util.PsiTreeUtil import groovy.lang.Closure -import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.* +import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_ARTIFACT_HANDLER +import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_PROJECT import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression -import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil -import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightField -import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder -import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightParameter import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyClosurePattern import org.jetbrains.plugins.groovy.lang.psi.patterns.groovyClosure import org.jetbrains.plugins.groovy.lang.psi.patterns.psiMethod -import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.DelegatesToInfo /** @@ -34,53 +20,10 @@ class GradleArtifactsContributor : GradleMethodContextContributor { val artifactsClosure: GroovyClosurePattern = groovyClosure().inMethod(psiMethod(GRADLE_API_PROJECT, "artifacts")) } - override fun process(methodCallInfo: List, - processor: PsiScopeProcessor, - state: ResolveState, - place: PsiElement): Boolean { - val methodName = if (methodCallInfo.isNotEmpty()) methodCallInfo[0] else null - if (methodName != null && place is GrReferenceExpression && psiElement().inside(artifactsClosure).accepts(place)) { - val text = place.text - if (!methodCallInfo.contains(text)) { - return false - } - // assuming that the method call is addition of an artifact to the given configuration. - if (!processArtifactAddition(processor, state, place)) return false - } - return true - } - override fun getDelegatesToInfo(closure: GrClosableBlock): DelegatesToInfo? { if (artifactsClosure.accepts(closure)) { return DelegatesToInfo(TypesUtil.createType(GRADLE_API_ARTIFACT_HANDLER, closure), Closure.DELEGATE_FIRST) } return null } - - private fun processArtifactAddition(processor: PsiScopeProcessor, - state: ResolveState, - place: PsiElement): Boolean { - val name = ResolveUtil.getNameHint(processor) ?: return true - val groovyPsiManager = GroovyPsiManager.getInstance(place.project) - val artifactHandlerClass = JavaPsiFacade.getInstance(place.project).findClass(GRADLE_API_ARTIFACT_HANDLER, place.resolveScope) - ?: return true - - val call = PsiTreeUtil.getParentOfType(place, GrMethodCall::class.java) ?: return true - val returnClass = groovyPsiManager.createTypeByFQClassName(GRADLE_API_PUBLISH_ARTIFACT, place.resolveScope) ?: return true - val type = PsiType.getJavaLangObject(place.manager, place.resolveScope).createArrayType() - val builder = GrLightMethodBuilder(place.manager, name).apply { - containingClass = artifactHandlerClass - addParameter(GrLightParameter("artifactNotation", type, place)) - returnType = returnClass - } - val args = call.argumentList - - var argsCount = GradleResolverUtil.getGrMethodArumentsCount(args) - argsCount++ // Configuration name is delivered as an argument. - - val method = artifactHandlerClass.findMethodsByName("add", false).firstOrNull { it.parameterList.parametersCount == argsCount } - if (method != null) builder.navigationElement = method - - return processor.execute(builder, state) - } } diff --git a/plugins/gradle/java/src/service/resolve/GradleSetterAsMethodContributor.kt b/plugins/gradle/java/src/service/resolve/GradleSetterAsMethodContributor.kt index 00da0871bcaa..5167df338ac2 100644 --- a/plugins/gradle/java/src/service/resolve/GradleSetterAsMethodContributor.kt +++ b/plugins/gradle/java/src/service/resolve/GradleSetterAsMethodContributor.kt @@ -8,8 +8,7 @@ import com.intellij.psi.scope.NameHint import com.intellij.psi.scope.ProcessorWithHints import com.intellij.psi.scope.PsiScopeProcessor import com.intellij.util.text.nullize -import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_ARTIFACTS_MODULE_DEPENDENCY -import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_PROJECT +import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.* import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrMethodWrapper import org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor import org.jetbrains.plugins.groovy.lang.resolve.getName @@ -20,6 +19,7 @@ class GradleSetterAsMethodContributor : NonCodeMembersContributor() { companion object { val knownDecoratedClasses = setOf( GRADLE_API_PROJECT, + GRADLE_API_CONFIGURABLE_PUBLISH_ARTIFACT, GRADLE_API_ARTIFACTS_MODULE_DEPENDENCY ) } diff --git a/plugins/gradle/java/testSources/dsl/GradleArtifactsTest.groovy b/plugins/gradle/java/testSources/dsl/GradleArtifactsTest.groovy index 45ad78bbcb2e..b119e101d07c 100644 --- a/plugins/gradle/java/testSources/dsl/GradleArtifactsTest.groovy +++ b/plugins/gradle/java/testSources/dsl/GradleArtifactsTest.groovy @@ -2,13 +2,16 @@ package org.jetbrains.plugins.gradle.dsl import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiType 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_ARTIFACT_HANDLER +import static org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_CONFIGURABLE_PUBLISH_ARTIFACT @CompileStatic class GradleArtifactsTest extends GradleHighlightingBaseTest implements ResolveTest { @@ -17,11 +20,23 @@ class GradleArtifactsTest extends GradleHighlightingBaseTest implements ResolveT void artifactsTest() { importProject("apply plugin: 'java'") new RunAll().append { - 'artifacts closure delegate'() + 'closure delegate'() } append { - 'artifacts member'() + 'member'() } append { - 'artifacts unresolved reference'() + 'unresolved reference'() + } append { + 'unresolved configuration reference'() + } append { + 'invalid artifact addition'() + } append { + 'artifact addition'() + } append { + 'configurable artifact addition'() + } append { + 'configuration delegate'() + } append { + 'configuration delegate method setter'() } run() } @@ -30,21 +45,76 @@ class GradleArtifactsTest extends GradleHighlightingBaseTest implements ResolveT return super.getParentCalls() + 'buildscript' } - void 'artifacts closure delegate'() { + void 'closure delegate'() { doTest('artifacts { }') { closureDelegateTest(GRADLE_API_ARTIFACT_HANDLER, 1) } } - void 'artifacts member'() { + void 'member'() { doTest('artifacts { add("conf", "notation") }') { methodTest(resolveTest(PsiMethod), "add", GRADLE_API_ARTIFACT_HANDLER) } } - void 'artifacts unresolved reference'() { + void 'unresolved reference'() { doTest('artifacts { foo }') { resolveTest(null) } } + + void 'unresolved configuration reference'() { + doTest('artifacts { compile }') { + resolveTest(null) + } + } + + void 'invalid artifact addition'() { + // foo configuration doesn't exist + doTest('artifacts { foo("artifactNotation") }') { + assertEmpty(elementUnderCaret(GrMethodCall).multiResolve(false)) + } + } + + void 'artifact addition'() { + def test = { + def call = elementUnderCaret(GrMethodCall) + def result = assertOneElement(call.multiResolve(false)) + methodTest(assertInstanceOf(result.element, PsiMethod), "compile", GRADLE_API_ARTIFACT_HANDLER) + assert result.applicable + assert call.type == PsiType.NULL + } + doTest('artifacts { compile("artifactNotation") }', test) + doTest('artifacts { compile("artifactNotation", "artifactNotation2", "artifactNotation3") }', test) + doTest('artifacts.compile("artifactNotation")', test) + doTest('artifacts.compile("artifactNotation", "artifactNotation2", "artifactNotation3")', test) + } + + void 'configurable artifact addition'() { + def test = { + def call = elementUnderCaret(GrMethodCall) + def result = assertOneElement(call.multiResolve(false)) + methodTest(assertInstanceOf(result.element, PsiMethod), "compile", GRADLE_API_ARTIFACT_HANDLER) + assert result.applicable + assert call.type.equalsToText(GRADLE_API_CONFIGURABLE_PUBLISH_ARTIFACT) + } + doTest('artifacts { compile("artifactNotation") {} }', test) + doTest('artifacts.compile("artifactNotation") {}', test) + } + + void 'configuration delegate'() { + doTest('artifacts { compile("artifactNotation") { } }') { + closureDelegateTest(GRADLE_API_CONFIGURABLE_PUBLISH_ARTIFACT, 1) + } + } + + void 'configuration delegate method setter'() { + doTest('artifacts { compile("artifactNotation") { name("hi") } }') { + def result = assertOneElement(elementUnderCaret(GrMethodCall).multiResolve(false)) + def method = assertInstanceOf(result.element, PsiMethod) + methodTest(method, 'name', GRADLE_API_CONFIGURABLE_PUBLISH_ARTIFACT) + def original = assertInstanceOf(method.navigationElement, PsiMethod) + methodTest(original, 'setName', GRADLE_API_CONFIGURABLE_PUBLISH_ARTIFACT) + } + } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/resolve/GradleCommonClassNames.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/resolve/GradleCommonClassNames.java index cc4155a8b24b..6609e960f68f 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/resolve/GradleCommonClassNames.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/resolve/GradleCommonClassNames.java @@ -19,6 +19,7 @@ public final class GradleCommonClassNames { @NonNls public static final String GRADLE_API_CONFIGURATION = "org.gradle.api.artifacts.Configuration"; @NonNls public static final String GRADLE_API_ARTIFACT_HANDLER = "org.gradle.api.artifacts.dsl.ArtifactHandler"; @NonNls public static final String GRADLE_API_PUBLISH_ARTIFACT = "org.gradle.api.artifacts.PublishArtifact"; + @NonNls public static final String GRADLE_API_CONFIGURABLE_PUBLISH_ARTIFACT = "org.gradle.api.artifacts.ConfigurablePublishArtifact"; @NonNls public static final String GRADLE_API_PUBLICATION_CONTAINER = "org.gradle.api.publish.PublicationContainer"; @NonNls public static final String GRADLE_API_DEPENDENCY_HANDLER = "org.gradle.api.artifacts.dsl.DependencyHandler"; @NonNls public static final String GRADLE_API_COMPONENT_METADATA_HANDLER = "org.gradle.api.artifacts.dsl.ComponentMetadataHandler"; diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/synthetic/GrLightMethodBuilder.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/synthetic/GrLightMethodBuilder.java index 375c76dfbed1..0a044abb0f64 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/synthetic/GrLightMethodBuilder.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/synthetic/GrLightMethodBuilder.java @@ -1,4 +1,4 @@ -// 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.groovy.lang.psi.impl.synthetic; import com.intellij.navigation.ItemPresentation; @@ -274,6 +274,11 @@ public class GrLightMethodBuilder extends LightElement implements GrMethod, Orig return addAndGetParameter(name, type, false); } + @NotNull + public GrLightParameter addAndGetParameter(@NotNull String name, @NotNull PsiType type) { + return addAndGetParameter(name, type, false); + } + @NotNull public GrLightParameter addAndGetOptionalParameter(@NotNull String name, @NotNull String type) { return addAndGetParameter(name, type, true);