[gradle] resolve/complete configurations via DependencyHandler instance

This commit is contained in:
Daniil Ovchinnikov
2019-03-29 19:56:34 +03:00
parent 1c2fd6d276
commit 5836a15f29
10 changed files with 137 additions and 124 deletions
@@ -35,8 +35,6 @@
<annotator language="Groovy" implementationClass="org.jetbrains.plugins.gradle.service.resolve.dsl.GradleDslAnnotator"/>
<weigher key="completion" implementationClass="org.jetbrains.plugins.gradle.service.resolve.dsl.GradleCompletionWeigher"
id="gradleWeigher" order="first"/>
<completion.contributor language="Groovy" order="first"
implementationClass="org.jetbrains.plugins.gradle.codeInsight.ConfigurationsCompletionContributor"/>
<completion.contributor language="Groovy" order="first"
implementationClass="org.jetbrains.plugins.gradle.codeInsight.PropertiesTasksCompletionContributor"/>
@@ -61,6 +59,7 @@
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleSettingsScriptContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleScriptMembersContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleProjectMembersContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleDependencyHandlerContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleNonCodeMembersContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleScriptContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleSetterAsMethodContributor"/>
@@ -1,70 +0,0 @@
// 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.
package org.jetbrains.plugins.gradle.codeInsight
import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.patterns.PlatformPatterns.psiElement
import com.intellij.psi.PsiElement
import com.intellij.util.ProcessingContext
import icons.GradleIcons
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.*
import org.jetbrains.plugins.gradle.service.resolve.GradleExtensionsContributor
import org.jetbrains.plugins.groovy.dsl.holders.NonCodeMembersHolder
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightVariable
import org.jetbrains.plugins.groovy.lang.psi.patterns.groovyClosure
import org.jetbrains.plugins.groovy.lang.psi.patterns.psiMethod
import java.lang.Double.MAX_VALUE
/**
* @author Vladislav.Soroka
*/
class ConfigurationsCompletionContributor : AbstractGradleCompletionContributor() {
class ConfigurationsCompletionProvider(val isScriptClasspath: Boolean = false) : CompletionProvider<CompletionParameters>() {
override fun addCompletions(params: CompletionParameters,
context: ProcessingContext,
result: CompletionResultSet) {
val position = params.position
val extensionsData = GradleExtensionsContributor.getExtensionsFor(position) ?: return
for (configuration in extensionsData.configurations.values) {
if (isScriptClasspath != configuration.scriptClasspath) continue
val confVar = object : GrLightVariable(position.manager, configuration.name, GRADLE_API_CONFIGURATION, position) {
override fun getNavigationElement(): PsiElement {
val navigationElement = super.getNavigationElement()
val description = if (isScriptClasspath && configuration.description == null) DEFAULT_SCRIPT_CLASSPATH_DESCRIPTION else configuration.description
navigationElement.putUserData(NonCodeMembersHolder.DOCUMENTATION, description)
return navigationElement
}
}
val elementBuilder = LookupElementBuilder.create(confVar, configuration.name)
.withIcon(GradleIcons.Gradle)
.withTypeText("Configuration")
result.addElement(PrioritizedLookupElement.withPriority(elementBuilder, MAX_VALUE))
}
}
}
init {
extend(CompletionType.BASIC, PROJECT_DEPENDENCIES_PLACE_PATTERN, ConfigurationsCompletionProvider())
extend(CompletionType.BASIC, SCRIPT_DEPENDENCIES_PLACE_PATTERN, ConfigurationsCompletionProvider(true))
}
companion object {
private const val DEFAULT_SCRIPT_CLASSPATH_DESCRIPTION = "The script classpath configuration used to compile and execute a build script. " +
"This classpath is also used to load the plugins which the build script uses."
private val PROJECT_DEPENDENCIES_PLACE_PATTERN = psiElement()
.and(GRADLE_FILE_PATTERN)
.withParent(GrReferenceExpression::class.java)
.withAncestor(4, groovyClosure().inMethod(psiMethod(GRADLE_API_PROJECT, "dependencies")))
private val SCRIPT_DEPENDENCIES_PLACE_PATTERN = psiElement()
.and(GRADLE_FILE_PATTERN)
.withParent(GrReferenceExpression::class.java)
.withAncestor(4, groovyClosure().inMethod(psiMethod(GRADLE_API_SCRIPT_HANDLER, "dependencies")))
}
}
@@ -1,6 +1,7 @@
// 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.util.ProcessingContext
import groovy.lang.Closure
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_PROJECT
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_SCRIPT_HANDLER
@@ -16,12 +17,17 @@ import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.DelegatesToInfo
*/
class GradleBuildScriptContributor : GradleMethodContextContributor {
companion object {
val buildscriptClosure: GroovyClosurePattern = groovyClosure().inMethod(psiMethod(GRADLE_API_PROJECT, "buildscript"))
val buildscriptClosure: GroovyClosurePattern = groovyClosure().inMethod(
psiMethod(GRADLE_API_PROJECT, "buildscript")
).inMethodResult(saveProjectType)
}
override fun getDelegatesToInfo(closure: GrClosableBlock): DelegatesToInfo? {
if (buildscriptClosure.accepts(closure)) {
return DelegatesToInfo(TypesUtil.createType(GRADLE_API_SCRIPT_HANDLER, closure), Closure.DELEGATE_FIRST)
val context = ProcessingContext()
if (buildscriptClosure.accepts(closure, context)) {
val scriptHandler = TypesUtil.createType(GRADLE_API_SCRIPT_HANDLER, closure)
val delegate = context.get(projectTypeKey)?.setType(scriptHandler, true) ?: scriptHandler
return DelegatesToInfo(delegate, Closure.DELEGATE_FIRST)
}
return null
}
@@ -1,25 +1,15 @@
// 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.patterns.StandardPatterns.or
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiElement
import com.intellij.psi.ResolveState
import com.intellij.psi.scope.ElementClassHint
import com.intellij.psi.scope.PsiScopeProcessor
import com.intellij.util.ProcessingContext
import groovy.lang.Closure
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.*
import org.jetbrains.plugins.gradle.service.resolve.GradleResolverUtil.canBeMethodOf
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock
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.GrLightMethodBuilder
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
/**
@@ -31,7 +21,7 @@ class GradleDependenciesContributor : GradleMethodContextContributor {
val dependenciesClosure: GroovyClosurePattern = groovyClosure().inMethod(or(
psiMethod(GRADLE_API_PROJECT, "dependencies"),
psiMethod(GRADLE_API_SCRIPT_HANDLER, "dependencies")
))
)).inMethodResult(saveProjectType)
val dependencyClosure: GroovyClosurePattern = groovyClosure().inMethod(psiMethod(GRADLE_API_DEPENDENCY_HANDLER, "add"))
val moduleClosure: GroovyClosurePattern = groovyClosure().inMethod(psiMethod(GRADLE_API_DEPENDENCY_HANDLER, "module"))
@@ -44,8 +34,13 @@ class GradleDependenciesContributor : GradleMethodContextContributor {
}
override fun getDelegatesToInfo(closure: GrClosableBlock): DelegatesToInfo? {
val context = ProcessingContext()
if (dependenciesClosure.accepts(closure, context)) {
val dependencyHandler = TypesUtil.createType(GRADLE_API_DEPENDENCY_HANDLER, closure)
val delegate = context.get(projectTypeKey)?.setType(dependencyHandler) ?: dependencyHandler
return DelegatesToInfo(delegate, Closure.DELEGATE_FIRST)
}
val fqn = when {
dependenciesClosure.accepts(closure) -> GRADLE_API_DEPENDENCY_HANDLER
dependencyClosure.accepts(closure) -> GRADLE_API_ARTIFACTS_MODULE_DEPENDENCY
moduleClosure.accepts(closure) -> GRADLE_API_ARTIFACTS_CLIENT_MODULE_DEPENDENCY
componentsClosure.accepts(closure) -> GRADLE_API_COMPONENT_METADATA_HANDLER
@@ -55,30 +50,4 @@ class GradleDependenciesContributor : GradleMethodContextContributor {
}
return DelegatesToInfo(TypesUtil.createType(fqn, closure), Closure.DELEGATE_FIRST)
}
override fun process(methodCallInfo: List<String>, processor: PsiScopeProcessor, state: ResolveState, place: PsiElement): Boolean {
val groovyPsiManager = GroovyPsiManager.getInstance(place.project)
val methodName = methodCallInfo.firstOrNull() ?: return true
val classHint = processor.getHint(ElementClassHint.KEY)
val shouldProcessMethods = ResolveUtil.shouldProcessMethods(classHint)
if (shouldProcessMethods && place is GrReferenceExpression && psiElement().inside(dependenciesClosure).accepts(place)) {
if (methodCallInfo.size == 2) {
val resolveScope = place.getResolveScope()
val psiClass = JavaPsiFacade.getInstance(place.project).findClass(GRADLE_API_DEPENDENCY_HANDLER, resolveScope) ?: return true
if (canBeMethodOf(methodName, psiClass)) {
return true
}
val returnClass = groovyPsiManager.createTypeByFQClassName(GRADLE_API_ARTIFACTS_DEPENDENCY, resolveScope)
val wrappedBase = GrLightMethodBuilder(place.manager, methodName).apply {
returnType = returnClass
containingClass = psiClass
}
wrappedBase.addParameter("dependencyNotation", TypesUtil.getJavaLangObject(place).createArrayType())
if (!processor.execute(wrappedBase, state)) return false
}
}
return true
}
}
}
@@ -0,0 +1,60 @@
// 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 icons.GradleIcons
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_DEPENDENCY_HANDLER
import org.jetbrains.plugins.gradle.settings.GradleExtensionsSettings.GradleConfiguration
import org.jetbrains.plugins.gradle.util.GradleBundle
import org.jetbrains.plugins.groovy.dsl.holders.NonCodeMembersHolder
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder
import org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor
import org.jetbrains.plugins.groovy.lang.resolve.getName
import org.jetbrains.plugins.groovy.lang.resolve.shouldProcessMethods
class GradleDependencyHandlerContributor : NonCodeMembersContributor() {
override fun getParentClassName(): String? = GRADLE_API_DEPENDENCY_HANDLER
override fun processDynamicElements(qualifierType: PsiType,
clazz: PsiClass?,
processor: PsiScopeProcessor,
place: PsiElement,
state: ResolveState) {
if (qualifierType !is GradleProjectAwareType) return
if (clazz == null) return
if (!processor.shouldProcessMethods()) return
val data = GradleExtensionsContributor.getExtensionsFor(place) ?: return
val methodName = processor.getName(state)
val manager = place.manager
val objectVarargType = PsiEllipsisType(TypesUtil.getJavaLangObject(place))
val configurations = if (methodName == null) data.configurations.values else listOf(data.configurations[methodName] ?: return)
for (configuration in configurations) {
if (configuration.scriptClasspath != qualifierType.buildscript) continue
val configurationName = configuration.name ?: continue
val method = GrLightMethodBuilder(manager, configurationName).apply {
methodKind = GradleArtifactHandlerContributor.ourMethodKind
containingClass = clazz
returnType = null
addParameter("dependencyNotation", objectVarargType)
setBaseIcon(GradleIcons.Gradle)
putUserData(NonCodeMembersHolder.DOCUMENTATION, configuration.getDescription())
}
if (!processor.execute(method, state)) return
}
}
private fun GradleConfiguration.getDescription(): String? {
if (description == null && scriptClasspath && name == "classpath") {
return GradleBundle.message("gradle.codeInsigt.buildscript.classpath.description")
}
else {
return description
}
}
}
@@ -8,8 +8,9 @@ import com.intellij.psi.PsiType
import com.intellij.psi.search.GlobalSearchScope
// TODO track exact project id to obtain project extension data
class GradleProjectAwareType(
private val delegate: PsiClassType
class GradleProjectAwareType (
private val delegate: PsiClassType,
val buildscript: Boolean
) : PsiClassType(LanguageLevel.HIGHEST) {
override fun isValid(): Boolean = delegate.isValid
@@ -17,7 +18,7 @@ class GradleProjectAwareType(
override fun resolve(): PsiClass? = delegate.resolve()
override fun resolveGenerics(): ClassResolveResult = delegate.resolveGenerics()
override fun getParameters(): Array<PsiType> = delegate.parameters
override fun rawType(): PsiClassType = GradleProjectAwareType(delegate.rawType())
override fun rawType(): PsiClassType = GradleProjectAwareType(delegate.rawType(), buildscript)
override fun getClassName(): String = delegate.className
override fun getCanonicalText(): String = delegate.canonicalText
@@ -27,5 +28,11 @@ class GradleProjectAwareType(
override fun getLanguageLevel(): LanguageLevel = delegate.languageLevel
override fun setLanguageLevel(languageLevel: LanguageLevel): PsiClassType = error("must not be called")
fun setType(delegate: PsiClassType): GradleProjectAwareType = GradleProjectAwareType(delegate)
fun setType(delegate: PsiClassType): GradleProjectAwareType {
return GradleProjectAwareType(delegate, buildscript)
}
fun setType(delegate: PsiClassType, buildscript: Boolean): GradleProjectAwareType {
return GradleProjectAwareType(delegate, buildscript)
}
}
@@ -14,7 +14,8 @@ class GradleProjectCallTypeCalculator : GrCallTypeCalculator {
private val methodNames = setOf(
"getProject",
"getArtifacts",
"getTasks"
"getTasks",
"getDependencies"
)
override fun getType(receiver: PsiType?, method: PsiMethod, arguments: Arguments?, context: PsiElement): PsiType? {
@@ -21,7 +21,7 @@ class GradleScriptMembersContributor : NonCodeMembersContributor() {
place: PsiElement,
state: ResolveState) {
if (aClass == null || !aClass.isResolvedInGradleScript()) return
val delegate = GradleProjectAwareType(createType(GRADLE_API_PROJECT, place.containingFile))
val delegate = GradleProjectAwareType(createType(GRADLE_API_PROJECT, place.containingFile), false)
delegate.processReceiverType(processor, state, place)
}
}
@@ -16,12 +16,12 @@ class GradleDependenciesTest extends GradleHighlightingBaseTest implements Resol
@Override
protected List<String> getParentCalls() {
return super.getParentCalls() + 'buildscript'
return []
}
@Test
void dependenciesTest() {
importProject("")
importProject("apply plugin: 'java'")
new RunAll().append {
'dependencies delegate'()
} append {
@@ -38,6 +38,14 @@ class GradleDependenciesTest extends GradleHighlightingBaseTest implements Resol
'modules delegate'()
} append {
'modules module delegate'()
} append {
'classpath configuration'()
} append {
'compile configuration'()
} append {
'buildscript classpath configuration'()
} append {
'buildscript compile configuration'()
} run()
}
@@ -96,4 +104,34 @@ class GradleDependenciesTest extends GradleHighlightingBaseTest implements Resol
closureDelegateTest(GRADLE_API_COMPONENT_MODULE_METADATA_DETAILS, 1)
}
}
void 'classpath configuration'() {
doTest('dependencies { <caret>classpath("hi") }') {
resolveTest(null)
}
}
void 'compile configuration'() {
doTest('dependencies { <caret>compile("hi") }') {
methodTest(resolveTest(PsiMethod), "compile", GRADLE_API_DEPENDENCY_HANDLER)
}
}
void 'compile confiduration via property'() {
doTest('dependencies.<caret>testCompile("hi")') {
methodTest(resolveTest(PsiMethod), "testCompile", GRADLE_API_DEPENDENCY_HANDLER)
}
}
void 'buildscript classpath configuration'() {
doTest('buildscript { dependencies { <caret>classpath("hi") } }') {
methodTest(resolveTest(PsiMethod), "classpath", GRADLE_API_DEPENDENCY_HANDLER)
}
}
void 'buildscript compile configuration'() {
doTest('buildscript { dependencies { <caret>compile("hi") } }') {
resolveTest(null)
}
}
}
@@ -46,6 +46,9 @@ gradle.codeInsight.action.apply_plugin.popup.title=Choose plugin
gradle.codeInsight.action.add_maven_dependency.text=Add Maven artifact dependency
gradle.codeInsight.action.add_maven_dependency.description=Add selected Maven artifact dependency to the project
gradle.codeInsigt.buildscript.classpath.description=The script classpath configuration used to compile and execute a build script. \
This classpath is also used to load the plugins which the build script uses.
gradle.runner=Runner
gradle.runner.toggle.tree.text.action.name=Toggle tasks executions/text mode
gradle.preferred_test_runner.CHOOSE_PER_TEST=Choose per test