[gradle] Extract interaction with gradle-dsl to an external module

GitOrigin-RevId: ef208df9e0f2170509c28c2d64ab535facd9ba8e
This commit is contained in:
Konstantin Nisht
2022-11-24 18:09:42 +03:00
committed by intellij-monorepo-bot
parent fd8407c46e
commit 763d9e1c97
18 changed files with 373 additions and 406 deletions

1
.idea/modules.xml generated
View File

@@ -602,6 +602,7 @@
<module fileurl="file://$PROJECT_DIR$/plugins/full-line/local/intellij.fullLine.local.iml" filepath="$PROJECT_DIR$/plugins/full-line/local/intellij.fullLine.local.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/full-line/python/intellij.fullLine.python.iml" filepath="$PROJECT_DIR$/plugins/full-line/python/intellij.fullLine.python.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/gradle/intellij.gradle.iml" filepath="$PROJECT_DIR$/plugins/gradle/intellij.gradle.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/gradle/intellij.gradle.analysis/intellij.gradle.analysis.iml" filepath="$PROJECT_DIR$/plugins/gradle/intellij.gradle.analysis/intellij.gradle.analysis.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/gradle/intellij.gradle.common.iml" filepath="$PROJECT_DIR$/plugins/gradle/intellij.gradle.common.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/gradle/gradle-dependency-updater/intellij.gradle.dependencyUpdater.iml" filepath="$PROJECT_DIR$/plugins/gradle/gradle-dependency-updater/intellij.gradle.dependencyUpdater.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/gradle/java/intellij.gradle.java.iml" filepath="$PROJECT_DIR$/plugins/gradle/java/intellij.gradle.java.iml" />

View File

@@ -200,5 +200,6 @@
<orderEntry type="module" module-name="intellij.fullLine.kotlin" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.performanceTesting" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.smart.update" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.gradle.analysis" scope="RUNTIME" />
</component>
</module>

View File

@@ -75,6 +75,7 @@ val IDEA_BUNDLED_PLUGINS: PersistentList<String> = JB_BUNDLED_PLUGINS + persiste
"intellij.android.gradle.dsl",
"intellij.gradle.java",
"intellij.gradle.java.maven",
"intellij.gradle.analysis",
"intellij.vcs.git",
"intellij.vcs.svn",
"intellij.vcs.hg",

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="intellij.android.gradle.dsl" />
<orderEntry type="module" module-name="intellij.gradle.java" />
<orderEntry type="module" module-name="intellij.java.psi" />
<orderEntry type="module" module-name="intellij.java.psi.impl" />
</component>
</module>

View File

@@ -0,0 +1,16 @@
<idea-plugin implementation-detail="true">
<name>Gradle-Java-Analysis</name>
<id>org.jetbrains.plugins.gradle.analysis</id>
<description>
A plugin, providing additional static analysis of buildscripts with help of Gradle DSL
</description>
<vendor>JetBrains</vendor>
<depends>org.jetbrains.plugins.gradle</depends>
<depends>org.jetbrains.idea.gradle.dsl</depends>
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
<externallyHandledExtensions implementation="org.jetbrains.plugins.gradle.service.resolve.static.GradleDslVersionCatalogHandler"/>
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,27 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gradle.service.resolve.static
import com.android.tools.idea.gradle.dsl.api.ProjectBuildModel
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import org.jetbrains.plugins.gradle.service.resolve.GradleVersionCatalogHandler
class GradleDslVersionCatalogHandler : GradleVersionCatalogHandler {
override fun getExternallyHandledExtension(project: Project): Set<String> {
// todo
return getVersionCatalogFiles(project).takeIf { it.isNotEmpty() }?.let { setOf("libs") } ?: emptySet()
}
override fun getVersionCatalogFiles(project: Project): Map<String, VirtualFile> {
return ProjectBuildModel.get(project).context.rootProjectFile?.versionCatalogFiles?.associate { it.catalogName to it.file } ?: emptyMap()
}
override fun getAccessorClass(context: PsiElement, catalogName: String): PsiClass? {
val project = context.project
val scope = context.resolveScope
val versionCatalogModel = ProjectBuildModel.get(project).versionCatalogModel ?: return null
return SyntheticVersionCatalogAccessor(project, scope, versionCatalogModel, catalogName)
}
}

View File

@@ -0,0 +1,156 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gradle.service.resolve.static
import com.android.tools.idea.gradle.dsl.api.GradleVersionCatalogModel
import com.android.tools.idea.gradle.dsl.api.ext.GradlePropertyModel
import com.intellij.lang.java.JavaLanguage
import com.intellij.lang.java.beans.PropertyKind
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.*
import com.intellij.psi.impl.light.LightClass
import com.intellij.psi.impl.light.LightMethodBuilder
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PropertyUtilBase
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames
/**
* Serves as a client for PSI infrastructure and as a layer over TOML version catalog files at the same time
*/
class SyntheticVersionCatalogAccessor(project: Project, scope: GlobalSearchScope, model: GradleVersionCatalogModel, className: String) :
LightClass(JavaPsiFacade.getInstance(project).findClass(CommonClassNames.JAVA_LANG_OBJECT, scope)!!) {
private val libraries: Array<PsiMethod> =
SyntheticAccessorBuilder(project, scope, className, Kind.LIBRARY)
.buildMethods(this, model.libraries().properties.map(::PropertyModelGraphNode), "")
.toTypedArray()
private val plugins: PsiMethod = SyntheticAccessorBuilder(project, scope, className, Kind.PLUGIN)
.buildEnclosingMethod(this, model.plugins().properties, "plugins")
private val versions: PsiMethod = SyntheticAccessorBuilder(project, scope, className, Kind.VERSION)
.buildEnclosingMethod(this, model.versions().properties, "versions")
private val bundles: PsiMethod = SyntheticAccessorBuilder(project, scope, className, Kind.BUNDLE)
.buildEnclosingMethod(this, emptyList(), "bundles")
private val className = "LibrariesFor${StringUtil.capitalize(className)}"
override fun getMethods(): Array<PsiMethod> {
return libraries + arrayOf(plugins, versions, bundles)
}
override fun getQualifiedName(): String {
return "org.gradle.accessors.dm.$className"
}
override fun getName(): String = className
companion object {
private enum class Kind(val prefix: String) {
LIBRARY("Library"), PLUGIN("Plugin"), BUNDLE("Bundle"), VERSION("Version")
}
private class SyntheticAccessorBuilder(val project: Project, val scope: GlobalSearchScope, val className: String, val kind: Kind) {
private fun buildSyntheticInnerClass(mapping: List<GraphNode>,
containingClass: PsiClass,
name: String): LightClass {
val clazz = object : LightClass(JavaPsiFacade.getInstance(project).findClass(CommonClassNames.JAVA_LANG_OBJECT, scope)!!) {
private val methods = buildMethods(this, mapping, name).toTypedArray()
override fun getMethods(): Array<out PsiMethod> {
return methods
}
override fun getContainingClass(): PsiClass {
return containingClass
}
override fun getName(): String {
return name + kind.prefix + "Accessors"
}
override fun getQualifiedName(): String {
return "org.gradle.accessors.dm.LibrariesFor${StringUtil.capitalize(className)}.${name}${kind.prefix}Accessors"
}
}
return clazz
}
fun buildMethods(constructedClass: PsiClass, model: List<GraphNode>, prefix: String): List<LightMethodBuilder> {
val container = mutableListOf<LightMethodBuilder>()
for (propertyModel in model) {
val name = propertyModel.getName()
val getterName = PropertyUtilBase.getAccessorName(name, PropertyKind.GETTER)
val method = LightMethodBuilder(PsiManager.getInstance(project), JavaLanguage.INSTANCE, getterName)
method.containingClass = constructedClass
val innerModel = propertyModel.getChildren()
if (innerModel != null && !innerModel.containsKey("version") && !innerModel.containsKey("module")) {
val syntheticClass = buildSyntheticInnerClass(innerModel.values.toList(), constructedClass,
prefix + StringUtil.capitalize(name))
method.setMethodReturnType(PsiElementFactory.getInstance(project).createType(syntheticClass, PsiSubstitutor.EMPTY))
}
else {
val fqn = when (kind) {
Kind.LIBRARY -> GradleCommonClassNames.GRADLE_API_ARTIFACTS_MINIMAL_EXTERNAL_MODULE_DEPENDENCY
Kind.PLUGIN -> GradleCommonClassNames.GRADLE_PLUGIN_USE_PLUGIN_DEPENDENCY
Kind.BUNDLE -> GradleCommonClassNames.GRADLE_API_ARTIFACTS_EXTERNAL_MODULE_DEPENDENCY_BUNDLE
Kind.VERSION -> CommonClassNames.JAVA_LANG_STRING
}
val provider = JavaPsiFacade.getInstance(project).findClass(GradleCommonClassNames.GRADLE_API_PROVIDER_PROVIDER, scope)
?: continue
val minimalDependency = PsiClassType.getTypeByName(fqn, project, scope)
method.setMethodReturnType(PsiElementFactory.getInstance(project).createType(provider, minimalDependency))
}
container.add(method)
}
return container
}
fun buildEnclosingMethod(constructedClass: PsiClass, model: List<GradlePropertyModel>, enclosingMethodName: String): PsiMethod {
val accessorName = PropertyUtilBase.getAccessorName(enclosingMethodName, PropertyKind.GETTER)
val method = LightMethodBuilder(PsiManager.getInstance(project), JavaLanguage.INSTANCE, accessorName)
method.containingClass = constructedClass
val graph =
if (kind == Kind.VERSION) distributeNames(model.map { it.name.split("_", "-") }) ?: emptyList()
else model.map(::PropertyModelGraphNode)
val syntheticClass = buildSyntheticInnerClass(graph, constructedClass, "")
method.setMethodReturnType(PsiElementFactory.getInstance(project).createType(syntheticClass, PsiSubstitutor.EMPTY))
return method
}
}
private interface GraphNode {
fun getName(): String
fun getChildren(): Map<String, GraphNode>?
}
private class PropertyModelGraphNode(val model: GradlePropertyModel) : GraphNode {
override fun getName(): String = model.name
override fun getChildren(): Map<String, GraphNode>? = model.getValue(
GradlePropertyModel.MAP_TYPE)?.mapValues { PropertyModelGraphNode(it.value) }
}
private fun distributeNames(continuation: List<List<String>>): List<PrefixBasedGraphNode>? {
if (continuation.isEmpty()) {
return null
}
val grouped = continuation.groupBy({ it[0] }) { it.drop(1).takeIf(List<String>::isNotEmpty) }.mapValues { it.value.filterNotNull() }
return grouped.map { (name, cont) ->
val node = distributeNames(cont)
PrefixBasedGraphNode(name, node)
}
}
private class PrefixBasedGraphNode(val rootName: String, val nested: List<PrefixBasedGraphNode>?) : GraphNode {
override fun getName(): String = rootName
override fun getChildren(): Map<String, GraphNode>? = nested?.associateBy { it.rootName }
}
}
}

View File

@@ -1,6 +1,6 @@
<idea-plugin>
<depends optional="true" config-file="gradle-groovy-toml-integration.xml">org.toml.lang</depends>
<depends>com.intellij.completion.ml.ranking</depends>
<depends optional="true" config-file="gradle-groovy-toml-integration.xml">org.toml.lang</depends>
<extensionPoints>
<extensionPoint qualifiedName="org.jetbrains.plugins.gradle.resolve.contributor" interface="org.jetbrains.plugins.gradle.service.resolve.GradleMethodContextContributor"
@@ -25,6 +25,7 @@
<frameworkSupport implementation="org.jetbrains.plugins.gradle.frameworkSupport.GradleGroovyFrameworkSupportProvider"/>
<kotlinDslFrameworkSupport implementation="org.jetbrains.plugins.gradle.frameworkSupport.KotlinDslGradleGroovyFrameworkSupportProvider" />
<testLocationCustomizer implementation="org.jetbrains.plugins.gradle.execution.test.runner.spock.SpockGradleTestLocationCustomizer"/>
<!--<dslInspectionProvider language="Groovy" implementationClass="org.jetbrains.plugins.gradle.codeInspection.groovy.GroovyGradleDslInspectionProvider"/>-->
</extensions>
<extensions defaultExtensionNs="com.intellij">
@@ -66,38 +67,6 @@
enabledByDefault="true" level="WARNING"
implementationClass="org.jetbrains.plugins.gradle.codeInspection.JCenterRepositoryInspection"/>
<localInspection language="Groovy" groupPath="Gradle" shortName="DependencyNotationArgument"
bundle="messages.GradleInspectionBundle"
key="inspection.display.name.unrecognized.dependency.notation" groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle"
enabledByDefault="true" level="WARNING"
implementationClass="org.jetbrains.plugins.gradle.codeInspection.GradleIncorrectDependencyNotationArgumentInspection"/>
<localInspection language="Groovy" groupPath="Gradle" shortName="ForeignDelegate"
bundle="messages.GradleInspectionBundle"
key="inspection.display.name.foreign.delegate" groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle"
enabledByDefault="true" level="WEAK WARNING"
implementationClass="org.jetbrains.plugins.gradle.codeInspection.GradleForeignDelegateInspection"/>
<localInspection language="Groovy" groupPath="Gradle" shortName="IncorrectPluginDslStructure"
bundle="messages.GradleInspectionBundle"
key="inspection.display.name.incorrect.plugin.dsl.structure" groupKey="inspection.validity" groupBundle="messages.GroovyBundle"
enabledByDefault="true" level="ERROR"
implementationClass="org.jetbrains.plugins.gradle.codeInspection.GradlePluginDslStructureInspection"/>
<localInspection language="Groovy" groupPath="Gradle" shortName="ConfigurationAvoidance"
bundle="messages.GradleInspectionBundle"
key="inspection.display.name.configuration.avoidance" groupKey="inspection.best.practises" groupBundle="messages.GradleInspectionBundle"
enabledByDefault="true" level="WARNING"
implementationClass="org.jetbrains.plugins.gradle.codeInspection.GradleConfigurationAvoidanceInspection"/>
<localInspection language="Groovy" groupPath="Gradle" shortName="DeprecatedConfigurations"
bundle="messages.GradleInspectionBundle"
key="inspection.display.name.deprecated.configurations" groupKey="inspection.validity" groupBundle="messages.GroovyBundle"
enabledByDefault="true" level="WARNING"
implementationClass="org.jetbrains.plugins.gradle.codeInspection.GradleDeprecatedConfigurationInspection"/>
<runConfigurationProducer implementation="org.jetbrains.plugins.gradle.execution.GradleGroovyScriptRunConfigurationProducer"
order="last"/>
<runLineMarkerContributor language="Groovy" id="gradleGroovyRunLineMarkerProvider"

View File

@@ -1,5 +1,7 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<gotoDeclarationHandler implementation="org.jetbrains.plugins.gradle.service.navigation.GradleVersionCatalogTomlAwareGotoDeclarationHandler"/>
<!--<gotoDeclarationHandler implementation="org.jetbrains.plugins.gradle.service.toml.GradleVersionCatalogTomlAwareGotoDeclarationHandler"/>-->
<!--<referencesSearch implementation="org.jetbrains.plugins.gradle.service.toml.GradleGroovyVersionCatalogReferencesSearcher"/>-->
</extensions>
</idea-plugin>

View File

@@ -1,5 +1,6 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<referencesSearch implementation="org.jetbrains.plugins.gradle.findUsages.GradlePropertyReferencesSearcher"/>
<!--<properties.implicitPropertyUsageProvider implementation="org.jetbrains.plugins.gradle.findUsages.GradleImplicitPropertyUsageProvider"/>-->
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,24 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<!--<elementDescriptionProvider implementation="org.jetbrains.plugins.gradle.toml.navigation.VersionCatalogDescriptionProvider"/>-->
<!--<findUsagesHandlerFactory implementation="org.jetbrains.plugins.gradle.toml.findUsages.GradleVersionCatalogFindUsagesFactory"/>-->
<!--<psi.referenceContributor language="TOML"-->
<!-- implementation="org.jetbrains.plugins.gradle.toml.navigation.VersionCatalogReferenceContributor"/>-->
<!--<renameInputValidator implementation="org.jetbrains.plugins.gradle.toml.navigation.VersionCatalogRenameInputValidator"/>-->
<!--<typedHandler implementation="org.jetbrains.plugins.gradle.toml.completion.VersionCatalogTypedHandler" id="tomlVersionCatalogAuto"/>-->
<!--<completion.contributor language="TOML"-->
<!-- implementationClass="org.jetbrains.plugins.gradle.toml.completion.VersionCatalogCompletionContributor"/>-->
<!--<localInspection language="TOML" groupPath="Gradle" shortName="UnusedVersionCatalogEntry"-->
<!-- bundle="messages.GradleInspectionBundle" key="inspection.display.name.unused.version.catalog.entry"-->
<!-- groupBundle="messages.GradleInspectionBundle" groupKey="inspection.style"-->
<!-- enabledByDefault="true"-->
<!-- implementationClass="org.jetbrains.plugins.gradle.codeInspection.toml.UnusedVersionCatalogEntryInspection"/>-->
</extensions>
</idea-plugin>

View File

@@ -17,6 +17,7 @@
<depends optional="true" config-file="gradle-coverage-integration.xml">Coverage</depends>
<depends optional="true" config-file="gradle-groovy-integration.xml">org.intellij.groovy</depends>
<depends optional="true" config-file="gradle-properties-integration.xml">com.intellij.properties</depends>
<depends optional="true" config-file="gradle-toml-integration.xml">org.toml.lang</depends>
<resource-bundle>messages.GradleInspectionBundle</resource-bundle>
@@ -31,6 +32,11 @@
dynamic="true"/>
<extensionPoint name="testLocationCustomizer" interface="org.jetbrains.plugins.gradle.execution.test.runner.GradleTestLocationCustomizer"
dynamic="true"/>
<extensionPoint name="externallyHandledExtensions" interface="org.jetbrains.plugins.gradle.service.resolve.GradleVersionCatalogHandler"
dynamic="true"/>
<!--<extensionPoint name="dslInspectionProvider" beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">-->
<!-- <with attribute="implementationClass" implements="org.jetbrains.plugins.gradle.codeInspection.GradleDslInspectionProvider"/>-->
<!--</extensionPoint>-->
</extensionPoints>
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
@@ -60,8 +66,6 @@
<externalProjectDataService implementation="org.jetbrains.plugins.gradle.service.project.data.BuildClasspathModuleGradleDataService"/>
<projectService serviceImplementation="org.jetbrains.plugins.gradle.service.GradleBuildClasspathManager"/>
<properties.implicitPropertyUsageProvider implementation="org.jetbrains.plugins.gradle.findUsages.GradleImplicitPropertyUsageProvider"/>
<compiler.updateResourcesBuildContributor
implementation="org.jetbrains.plugins.gradle.execution.build.GradleUpdateResourcesBuildContributor"/>
<projectService serviceImplementation="org.jetbrains.plugins.gradle.config.GradleResourceCompilerConfigurationGenerator"/>
@@ -102,5 +106,40 @@
description="Specifies port at which Gradle target server process will wait for debugger connections. -1 means disabled feature."/>
<registryKey key="gradle.refresh.project.outputs" defaultValue="true"
description="After a Gradle task, do a shallow refresh of modules outputs in VFS"/>
<!--<localInspection language="" groupPath="Gradle" shortName="ConfigurationAvoidance"-->
<!-- bundle="messages.GradleInspectionBundle"-->
<!-- key="inspection.display.name.configuration.avoidance" groupKey="inspection.best.practises" groupBundle="messages.GradleInspectionBundle"-->
<!-- enabledByDefault="true" level="WARNING"-->
<!-- implementationClass="org.jetbrains.plugins.gradle.codeInspection.GradleConfigurationAvoidanceInspection"/>-->
<!--<localInspection language="" groupPath="Gradle" shortName="DependencyNotationArgument"-->
<!-- bundle="messages.GradleInspectionBundle"-->
<!-- key="inspection.display.name.unrecognized.dependency.notation" groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle"-->
<!-- enabledByDefault="true" level="WARNING"-->
<!-- implementationClass="org.jetbrains.plugins.gradle.codeInspection.GradleIncorrectDependencyNotationArgumentInspection"/>-->
<!--<localInspection language="" groupPath="Gradle" shortName="ForeignDelegate"-->
<!-- bundle="messages.GradleInspectionBundle"-->
<!-- key="inspection.display.name.foreign.delegate" groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle"-->
<!-- enabledByDefault="true" level="WEAK WARNING"-->
<!-- implementationClass="org.jetbrains.plugins.gradle.codeInspection.GradleForeignDelegateInspection"/>-->
<!--<localInspection language="" groupPath="Gradle" shortName="IncorrectPluginDslStructure"-->
<!-- bundle="messages.GradleInspectionBundle"-->
<!-- key="inspection.display.name.incorrect.plugin.dsl.structure" groupKey="inspection.validity" groupBundle="messages.GradleInspectionBundle"-->
<!-- enabledByDefault="true" level="ERROR"-->
<!-- implementationClass="org.jetbrains.plugins.gradle.codeInspection.GradlePluginDslStructureInspection"/>-->
<!--<localInspection language="" groupPath="Gradle" shortName="DeprecatedConfigurations"-->
<!-- bundle="messages.GradleInspectionBundle"-->
<!-- key="inspection.display.name.deprecated.configurations" groupKey="inspection.validity" groupBundle="messages.GradleInspectionBundle"-->
<!-- enabledByDefault="true" level="WARNING"-->
<!-- implementationClass="org.jetbrains.plugins.gradle.codeInspection.GradleDeprecatedConfigurationInspection"/>-->
</extensions>
</idea-plugin>

View File

@@ -1,173 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gradle.service.navigation
import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.parentOfType
import com.intellij.psi.util.parents
import com.intellij.util.asSafely
import com.intellij.util.containers.tail
import org.jetbrains.plugins.gradle.service.project.CommonGradleProjectResolverExtension
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames
import org.jetbrains.plugins.gradle.service.resolve.GradleExtensionProperty
import org.jetbrains.plugins.gradle.service.resolve.VersionCatalogsLocator
import org.jetbrains.plugins.gradle.service.resolve.getRootGradleProjectPath
import org.jetbrains.plugins.groovy.intentions.style.inference.resolve
import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils
import org.toml.lang.psi.TomlFile
import org.toml.lang.psi.TomlKeyValue
import org.toml.lang.psi.TomlRecursiveVisitor
import org.toml.lang.psi.TomlTable
class GradleVersionCatalogTomlAwareGotoDeclarationHandler : GotoDeclarationHandler {
override fun getGotoDeclarationTargets(sourceElement: PsiElement?, offset: Int, editor: Editor?): Array<PsiElement>? {
if (!Registry.`is`(CommonGradleProjectResolverExtension.GRADLE_VERSION_CATALOGS_DYNAMIC_SUPPORT, false)) {
return null
}
if (sourceElement == null) {
return null
}
val resolved = sourceElement.parentOfType<GrReferenceElement<*>>()?.resolve()
if (resolved is GradleExtensionProperty && resolved.name == "libs") {
val toml = findTomlFile(sourceElement, resolved.name)
if (toml != null) {
return arrayOf(toml)
}
}
if (resolved is PsiMethod && resolved.containingFile?.name?.startsWith("LibrariesFor") == true) {
val actualMethod = findFinishingNode(sourceElement) ?: resolved
return actualMethod.resolveInToml(sourceElement)?.let { arrayOf(it) }
}
return null
}
}
private fun findTomlFile(context: PsiElement, name: String): TomlFile? {
context.getRootGradleProjectPath()
val module = context.containingFile?.originalFile?.virtualFile?.let {
ProjectFileIndex.getInstance(context.project).getModuleForFile(it)
} ?: return null
val tomlPath = context.project.service<VersionCatalogsLocator>().getVersionCatalogsForModule(module)[name] ?: return null
val toml = VfsUtil.findFile(tomlPath, false) ?: return null
return PsiManager.getInstance(context.project).findFile(toml)?.asSafely<TomlFile>()
}
private fun PsiMethod.resolveInToml(context: PsiElement): PsiElement? {
val containingClasses = mutableListOf(containingClass ?: return null)
while (containingClasses.last().containingClass != null) {
containingClasses.add(containingClasses.last().containingClass!!)
}
containingClasses.reverse()
val name = containingClasses.first().name?.substringAfter(LIBRARIES_FOR_PREFIX) ?: return null
val toml = listOf(GroovyPropertyUtils.decapitalize(name), name).firstNotNullOfOrNull { findTomlFile(context, it) }
?: return null
val tomlVisitor = TomlVersionCatalogVisitor(containingClasses.tail(), this)
toml.accept(tomlVisitor)
return tomlVisitor.resolveTarget
}
private fun findFinishingNode(element: PsiElement) : PsiMethod? {
var topElement : PsiMethod? = null
for (ancestor in element.parents(true)) {
if (ancestor !is GrReferenceElement<*>) {
continue
}
val resolved = ancestor.resolve()
if (resolved is PsiMethod && resolved.containingFile.name.startsWith("LibrariesFor")) {
topElement = resolved
}
}
return topElement
}
private class TomlVersionCatalogVisitor(containingClasses: List<PsiClass>, val targetMethod: PsiMethod) : TomlRecursiveVisitor() {
private val containingClasses: MutableList<PsiClass> = ArrayList(containingClasses)
var resolveTarget: PsiElement? = null
override fun visitTable(element: TomlTable) {
val headerKind = element.header.key?.segments?.singleOrNull()?.name?.getTomlHeaderKind() ?: return
val firstClass = containingClasses.firstOrNull()
if (firstClass != null) {
val firstClassKind = firstClass.getTomlHeaderKind() ?: return
if (headerKind != firstClassKind) {
return
}
if (targetMethod.returnType?.resolve()?.qualifiedName != GradleCommonClassNames.GRADLE_API_PROVIDER_PROVIDER) {
return
}
return resolveAsComponent(element.entries)
}
else {
when (targetMethod.name) {
METHOD_GET_PLUGINS -> if (headerKind == TomlHeaderKind.PLUGINS) resolveTarget = element else return
METHOD_GET_BUNDLES -> if (headerKind == TomlHeaderKind.BUNDLES) resolveTarget = element else return
METHOD_GET_VERSIONS -> if (headerKind == TomlHeaderKind.VERSIONS) resolveTarget = element else return
else -> if (headerKind == TomlHeaderKind.LIBRARIES) resolveAsComponent(element.entries) else return
}
}
}
private fun resolveAsComponent(values: List<TomlKeyValue>) {
val camelCasedName = getCapitalizedAccessorName(targetMethod)
for (tomlEntry in values) {
val keyName =
tomlEntry.key.segments.firstOrNull()?.name?.split("_", "-")?.joinToString("", transform = GroovyPropertyUtils::capitalize)
?: continue
if (camelCasedName == keyName) {
resolveTarget = tomlEntry
return
}
}
}
}
private enum class TomlHeaderKind {
VERSIONS,
BUNDLES,
LIBRARIES,
PLUGINS
}
private fun PsiClass.getTomlHeaderKind(): TomlHeaderKind? {
val name = name ?: return null
return when {
name.endsWith(VERSION_ACCESSORS_SUFFIX) -> TomlHeaderKind.VERSIONS
name.endsWith(BUNDLE_ACCESSORS_SUFFIX) -> TomlHeaderKind.BUNDLES
name.endsWith(PLUGIN_ACCESSORS_SUFFIX) -> TomlHeaderKind.PLUGINS
name.endsWith(LIBRARY_ACCESSORS_SUFFIX) -> TomlHeaderKind.LIBRARIES
else -> null
}
}
private fun String.getTomlHeaderKind(): TomlHeaderKind? =
when (this) {
TOML_TABLE_VERSIONS -> TomlHeaderKind.VERSIONS
TOML_TABLE_LIBRARIES -> TomlHeaderKind.LIBRARIES
TOML_TABLE_BUNDLES -> TomlHeaderKind.BUNDLES
TOML_TABLE_PLUGINS -> TomlHeaderKind.PLUGINS
else -> null
}
private const val TOML_TABLE_VERSIONS = "versions"
private const val TOML_TABLE_LIBRARIES = "libraries"
private const val TOML_TABLE_BUNDLES = "bundles"
private const val TOML_TABLE_PLUGINS = "plugins"
private const val METHOD_GET_PLUGINS = "getPlugins"
private const val METHOD_GET_VERSIONS = "getVersions"
private const val METHOD_GET_BUNDLES = "getBundles"

View File

@@ -0,0 +1,46 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gradle.service.resolve
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import org.jetbrains.annotations.ApiStatus
/**
* Allows to handle gradle version catalogs
*/
@ApiStatus.Internal
interface GradleVersionCatalogHandler {
fun getExternallyHandledExtension(project: Project) : Set<String>
fun getVersionCatalogFiles(project: Project) : Map</*catalog name*/ String, /*catalog file*/ VirtualFile>
fun getAccessorClass(context: PsiElement, catalogName: String) : PsiClass?
}
fun getVersionCatalogFiles(project: Project) : Map<String, VirtualFile> {
val container = mutableMapOf<String, VirtualFile>()
for (extension in EP_NAME.extensionList) {
container.putAll(extension.getVersionCatalogFiles(project))
}
return container
}
fun getGradleStaticallyHandledExtensions(project: Project) : Set<String> {
val container = mutableSetOf<String>()
for (extension in EP_NAME.extensionList) {
container.addAll(extension.getExternallyHandledExtension(project))
}
return container
}
fun getVersionCatalogAccessor(context: PsiElement, name: String) : PsiClass? {
for (extension in EP_NAME.extensionList) {
return extension.getAccessorClass(context, name) ?: continue
}
return null
}
private val EP_NAME : ExtensionPointName<GradleVersionCatalogHandler> = ExtensionPointName.create("org.jetbrains.plugins.gradle.externallyHandledExtensions")

View File

@@ -0,0 +1,35 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gradle.service.resolve
import com.intellij.lang.properties.psi.PropertiesFile
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiUtilCore
import com.intellij.util.asSafely
import org.jetbrains.plugins.gradle.settings.GradleLocalSettings
import org.jetbrains.plugins.gradle.util.PROPERTIES_FILE_NAME
import org.jetbrains.plugins.gradle.util.getGradleUserHomePropertiesPath
import java.nio.file.Path
internal fun gradlePropertiesStream(place: PsiElement): Sequence<PropertiesFile> = sequence {
val externalRootProjectPath = place.getRootGradleProjectPath() ?: return@sequence
val userHomePropertiesFile = getGradleUserHomePropertiesPath()?.parent?.toString()?.getGradlePropertiesFile(place.project)
if (userHomePropertiesFile != null) {
yield(userHomePropertiesFile)
}
val projectRootPropertiesFile = externalRootProjectPath.getGradlePropertiesFile(place.project)
if (projectRootPropertiesFile != null) {
yield(projectRootPropertiesFile)
}
val localSettings = GradleLocalSettings.getInstance(place.project)
val installationDirectoryPropertiesFile = localSettings.getGradleHome(externalRootProjectPath)?.getGradlePropertiesFile(place.project)
if (installationDirectoryPropertiesFile != null) {
yield(installationDirectoryPropertiesFile)
}
}
private fun String.getGradlePropertiesFile(project: Project): PropertiesFile? {
val file = VfsUtil.findFile(Path.of(this), false)?.findChild(PROPERTIES_FILE_NAME)
return file?.let { PsiUtilCore.getPsiFile(project, it) }.asSafely<PropertiesFile>()
}

View File

@@ -1,26 +1,17 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gradle.service.resolve
import com.intellij.lang.properties.psi.PropertiesFile
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.patterns.PatternCondition
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiUtilCore
import com.intellij.util.ProcessingContext
import com.intellij.util.asSafely
import org.jetbrains.plugins.gradle.settings.GradleLocalSettings
import org.jetbrains.plugins.gradle.util.GradleConstants.EXTENSION
import org.jetbrains.plugins.gradle.util.PROPERTIES_FILE_NAME
import org.jetbrains.plugins.gradle.util.getGradleUserHomePropertiesPath
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyMethodResult
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass
import java.nio.file.Path
val projectTypeKey: Key<GradleProjectAwareType> = Key.create("gradle.current.project")
val saveProjectType: PatternCondition<GroovyMethodResult> = object : PatternCondition<GroovyMethodResult>("saveProjectContext") {
@@ -55,26 +46,4 @@ internal fun PsiElement.getLinkedGradleProjectPath() : String? {
internal fun PsiElement.getRootGradleProjectPath() : String? {
return ExternalSystemApiUtil.getExternalRootProjectPath(module)
}
internal fun gradlePropertiesStream(place: PsiElement): Sequence<PropertiesFile> = sequence {
val externalRootProjectPath = place.getRootGradleProjectPath() ?: return@sequence
val userHomePropertiesFile = getGradleUserHomePropertiesPath()?.parent?.toString()?.getGradlePropertiesFile(place.project)
if (userHomePropertiesFile != null) {
yield(userHomePropertiesFile)
}
val projectRootPropertiesFile = externalRootProjectPath.getGradlePropertiesFile(place.project)
if (projectRootPropertiesFile != null) {
yield(projectRootPropertiesFile)
}
val localSettings = GradleLocalSettings.getInstance(place.project)
val installationDirectoryPropertiesFile = localSettings.getGradleHome(externalRootProjectPath)?.getGradlePropertiesFile(place.project)
if (installationDirectoryPropertiesFile != null) {
yield(installationDirectoryPropertiesFile)
}
}
private fun String.getGradlePropertiesFile(project: Project): PropertiesFile? {
val file = VfsUtil.findFile(Path.of(this), false)?.findChild(PROPERTIES_FILE_NAME)
return file?.let { PsiUtilCore.getPsiFile(project, it) }.asSafely<PropertiesFile>()
}
}

View File

@@ -1,165 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gradle.dsl.versionCatalogs
import com.intellij.psi.util.parentOfType
import org.gradle.util.GradleVersion
import org.jetbrains.plugins.gradle.testFramework.GradleCodeInsightTestCase
import org.jetbrains.plugins.gradle.testFramework.GradleTestFixtureBuilder
import org.jetbrains.plugins.gradle.testFramework.annotations.BaseGradleVersionSource
import org.jetbrains.plugins.gradle.testFramework.util.withSettingsFile
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall
import org.junit.jupiter.api.Assertions.assertInstanceOf
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.params.ParameterizedTest
import org.toml.lang.psi.TomlFile
import org.toml.lang.psi.TomlKeyValue
import org.toml.lang.psi.TomlTable
class GradleVersionCatalogsResolveTest : GradleCodeInsightTestCase() {
@ParameterizedTest
@BaseGradleVersionSource
fun testNavigationToTomlFile(gradleVersion: GradleVersion) {
test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
testGotoDefinition("lib<caret>s") {
assertInstanceOf(TomlFile::class.java, it)
assertTrue((it as TomlFile).name == "libs.versions.toml")
}
}
}
@ParameterizedTest
@BaseGradleVersionSource
fun testNavigationToTomlFile2(gradleVersion: GradleVersion) {
test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
testGotoDefinition("lib<caret>s2") {
assertInstanceOf(GroovyPsiElement::class.java, it)
assertTrue(it.parentOfType<GrMethodCall>(true)!!.resolveMethod()!!.name == "libs2")
}
}
}
@ParameterizedTest
@BaseGradleVersionSource
fun testNavigationToCustomCatalog(gradleVersion: GradleVersion) {
test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
testGotoDefinition("lib<caret>s3") {
assertInstanceOf(GroovyPsiElement::class.java, it)
assertTrue(it.parentOfType<GrMethodCall>(true)!!.resolveMethod()!!.name == "libs3")
}
}
}
@ParameterizedTest
@BaseGradleVersionSource
fun testNavigationToTomlEntry(gradleVersion: GradleVersion) {
test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
testGotoDefinition("libs.groovy.cor<caret>e") {
assertInstanceOf(TomlKeyValue::class.java, it)
assertTrue((it as TomlKeyValue).key.text == "groovy-core")
}
}
}
@ParameterizedTest
@BaseGradleVersionSource
fun testNavigationToTomlEntry2(gradleVersion: GradleVersion) {
test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
testGotoDefinition("libs.groo<caret>vy.core") {
assertInstanceOf(TomlKeyValue::class.java, it)
assertTrue((it as TomlKeyValue).key.text == "groovy-core")
}
}
}
@ParameterizedTest
@BaseGradleVersionSource
fun testNavigationToTomlTable(gradleVersion: GradleVersion) {
test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
testGotoDefinition("libs.bun<caret>dles") {
assertInstanceOf(TomlTable::class.java, it)
assertTrue((it as TomlTable).header.text == "[bundles]")
}
}
}
@ParameterizedTest
@BaseGradleVersionSource
fun testNavigationToLibraryInSettings(gradleVersion: GradleVersion) {
test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
testGotoDefinition("libs3.foo.bar.ba<caret>z") {
assertInstanceOf(GroovyPsiElement::class.java, it)
assertTrue(it.parentOfType<GrMethodCall>(true)!!.argumentList.expressionArguments[0].text == "\"foo.bar.baz\"")
}
}
}
@ParameterizedTest
@BaseGradleVersionSource
fun testNavigationToLibraryInSettings2(gradleVersion: GradleVersion) {
test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
testGotoDefinition("libs3.foo.nn.m<caret>m") {
assertInstanceOf(GroovyPsiElement::class.java, it)
assertTrue(it.parentOfType<GrMethodCall>(true)!!.argumentList.expressionArguments[0].text == "\"foo-nn-mm\"")
}
}
}
@ParameterizedTest
@BaseGradleVersionSource
fun testNavigationWithCapitalLetters(gradleVersion: GradleVersion) {
test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
testGotoDefinition("libs2.getCheck().getCapital().getLe<caret>tter()") {
assertInstanceOf(TomlKeyValue::class.java, it)
assertTrue((it as TomlKeyValue).key.text == "check-Capital-Letter")
}
}
}
companion object {
private val BASE_VERSION_CATALOG_FIXTURE = GradleTestFixtureBuilder
.create("GradleVersionCatalogs-completion") {
withSettingsFile {
setProjectName("GradleVersionCatalogs-completion")
enableFeaturePreview("VERSION_CATALOGS")
addCode("""
dependencyResolutionManagement {
versionCatalogs {
libs2 {
from(files("gradle/my.toml"))
}
libs3 {
library("foo.bar.baz", "org.apache.groovy:groovy:4.0.0")
library("foo-nn-mm", "org.apache.groovy:groovy:4.0.0")
}
}
}
""".trimIndent())
}
withFile("gradle/libs.versions.toml", /* language=TOML */ """
[versions]
groovy = "3.0.5"
checkstyle = "8.37"
[libraries]
groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" }
groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" }
commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer="3.9" } }
[bundles]
groovy = ["groovy-core", "groovy-json", "groovy-nio"]
[plugins]
jmh = { id = "me.champeau.jmh", version = "0.6.5" }
""".trimIndent())
withFile("gradle/my.toml", /* language=TOML */ """
[libraries]
aa-bb-cc = { module = "org.apache.groovy:groovy", version = "4.0.0" }
check-Capital-Letter = { module = "org.apache.groovy:groovy", version = "4.0.0" }
""".trimIndent())
}
}
}

View File

@@ -26,6 +26,7 @@ public final class GradleCommonClassNames {
@NonNls public static final String GRADLE_API_COMPONENT_MODULE_METADATA_HANDLER = "org.gradle.api.artifacts.dsl.ComponentModuleMetadataHandler";
@NonNls public static final String GRADLE_API_COMPONENT_MODULE_METADATA_DETAILS = "org.gradle.api.artifacts.ComponentModuleMetadataDetails";
@NonNls public static final String GRADLE_API_ARTIFACTS_EXTERNAL_MODULE_DEPENDENCY = "org.gradle.api.artifacts.ExternalModuleDependency";
@NonNls public static final String GRADLE_API_ARTIFACTS_EXTERNAL_MODULE_DEPENDENCY_BUNDLE = "org.gradle.api.artifacts.ExternalModuleDependencyBundle";
@NonNls public static final String GRADLE_API_ARTIFACTS_PROJECT_DEPENDENCY = "org.gradle.api.artifacts.ProjectDependency";
@NonNls public static final String GRADLE_API_ARTIFACTS_SELF_RESOLVING_DEPENDENCY = "org.gradle.api.artifacts.SelfResolvingDependency";
@NonNls public static final String GRADLE_API_ARTIFACTS_CLIENT_MODULE_DEPENDENCY = "org.gradle.api.artifacts.ClientModule";
@@ -85,6 +86,7 @@ public final class GradleCommonClassNames {
@NonNls public static final String GRADLE_PROCESS_EXEC_SPEC = "org.gradle.process.ExecSpec";
@NonNls public static final String GRADLE_API_PROVIDER_PROPERTY = "org.gradle.api.provider.Property";
@NonNls public static final String GRADLE_API_PROVIDER_PROVIDER = "org.gradle.api.provider.Provider";
@NonNls public static final String GRADLE_PLUGIN_USE_PLUGIN_DEPENDENCY = "org.gradle.plugin.use.PluginDependency";
private GradleCommonClassNames() {
}