[gradle] resolve ArtifactHandler methods using imported configurations data

This commit is contained in:
Daniil Ovchinnikov
2019-03-25 20:23:10 +03:00
parent 2cdd8e485b
commit fc10708ccd
7 changed files with 151 additions and 68 deletions
@@ -63,6 +63,7 @@
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleScriptContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleSetterAsMethodContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleConventionsContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleArtifactHandlerContributor"/>
<delegatesToProvider implementation="org.jetbrains.plugins.gradle.service.resolve.GradleDelegatesToProvider"/>
<referenceTypeEnhancer implementation="org.jetbrains.plugins.gradle.service.resolve.NamedDomainObjectCollectionTypeEnhancer"/>
<unresolvedHighlightFilter implementation="org.jetbrains.plugins.gradle.service.resolve.GradleUnresolvedReferenceFilter"/>
@@ -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
}
}
}
@@ -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<String>,
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)
}
}
@@ -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
)
}
@@ -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 { <caret> }') {
closureDelegateTest(GRADLE_API_ARTIFACT_HANDLER, 1)
}
}
void 'artifacts member'() {
void 'member'() {
doTest('artifacts { <caret>add("conf", "notation") }') {
methodTest(resolveTest(PsiMethod), "add", GRADLE_API_ARTIFACT_HANDLER)
}
}
void 'artifacts unresolved reference'() {
void 'unresolved reference'() {
doTest('artifacts { <caret>foo }') {
resolveTest(null)
}
}
void 'unresolved configuration reference'() {
doTest('artifacts { <caret>compile }') {
resolveTest(null)
}
}
void 'invalid artifact addition'() {
// foo configuration doesn't exist
doTest('artifacts { <caret>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 { <caret>compile("artifactNotation") }', test)
doTest('artifacts { <caret>compile("artifactNotation", "artifactNotation2", "artifactNotation3") }', test)
doTest('artifacts.<caret>compile("artifactNotation")', test)
doTest('artifacts.<caret>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 { <caret>compile("artifactNotation") {} }', test)
doTest('artifacts.<caret>compile("artifactNotation") {}', test)
}
void 'configuration delegate'() {
doTest('artifacts { compile("artifactNotation") { <caret> } }') {
closureDelegateTest(GRADLE_API_CONFIGURABLE_PUBLISH_ARTIFACT, 1)
}
}
void 'configuration delegate method setter'() {
doTest('artifacts { compile("artifactNotation") { <caret>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)
}
}
}
@@ -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";
@@ -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);