diff --git a/plugins/gradle/java/intellij.gradle.java.tests.iml b/plugins/gradle/java/intellij.gradle.java.tests.iml
index f16699052543..c49e0b91e424 100644
--- a/plugins/gradle/java/intellij.gradle.java.tests.iml
+++ b/plugins/gradle/java/intellij.gradle.java.tests.iml
@@ -30,6 +30,7 @@
+
\ No newline at end of file
diff --git a/plugins/gradle/java/resources/META-INF/gradle-toml-integration.xml b/plugins/gradle/java/resources/META-INF/gradle-toml-integration.xml
index e0423f4c3f04..28959fcd834e 100644
--- a/plugins/gradle/java/resources/META-INF/gradle-toml-integration.xml
+++ b/plugins/gradle/java/resources/META-INF/gradle-toml-integration.xml
@@ -2,9 +2,9 @@
-
-
-
+
+
diff --git a/plugins/gradle/java/src/config/GradleBuildscriptSearchScope.kt b/plugins/gradle/java/src/config/GradleBuildscriptSearchScope.kt
new file mode 100644
index 000000000000..92c8fe059e1f
--- /dev/null
+++ b/plugins/gradle/java/src/config/GradleBuildscriptSearchScope.kt
@@ -0,0 +1,21 @@
+// 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.config
+
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.search.GlobalSearchScope
+import org.jetbrains.plugins.gradle.codeInspection.GradleInspectionBundle
+import org.jetbrains.plugins.gradle.util.GradleConstants
+
+class GradleBuildscriptSearchScope(project: Project) : GlobalSearchScope(project) {
+ override fun contains(file: VirtualFile): Boolean {
+ return GradleConstants.EXTENSION == file.extension || file.name.endsWith(GradleConstants.KOTLIN_DSL_SCRIPT_EXTENSION)
+ }
+ override fun isSearchInModuleContent(aModule: Module) = true
+ override fun isSearchInLibraries() = false
+
+ override fun getDisplayName(): String {
+ return GradleInspectionBundle.message("gradle.buildscript.search.scope")
+ }
+}
\ No newline at end of file
diff --git a/plugins/gradle/java/src/config/GradleUseScopeEnlarger.kt b/plugins/gradle/java/src/config/GradleUseScopeEnlarger.kt
index 4e3f1504e6cb..e054ca0c3f02 100644
--- a/plugins/gradle/java/src/config/GradleUseScopeEnlarger.kt
+++ b/plugins/gradle/java/src/config/GradleUseScopeEnlarger.kt
@@ -14,7 +14,6 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementFinder
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiReference
-import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.UseScopeEnlarger
import com.intellij.psi.search.searches.ReferencesSearch
@@ -45,13 +44,7 @@ class GradleUseScopeEnlarger : UseScopeEnlarger() {
if (!isInBuildSrc(project, virtualFile) && !isInGradleDistribution(project, virtualFile)) return null
- return object : GlobalSearchScope(element.project) {
- override fun contains(file: VirtualFile): Boolean {
- return GradleConstants.EXTENSION == file.extension || file.name.endsWith(GradleConstants.KOTLIN_DSL_SCRIPT_EXTENSION)
- }
- override fun isSearchInModuleContent(aModule: Module) = true
- override fun isSearchInLibraries() = false
- }
+ return GradleBuildscriptSearchScope(element.project)
}
private fun isInGradleDistribution(project: Project, file: VirtualFile) : Boolean {
diff --git a/plugins/gradle/java/src/toml/findUsages/GradleVersionCatalogFindUsagesFactory.kt b/plugins/gradle/java/src/toml/findUsages/GradleVersionCatalogFindUsagesFactory.kt
new file mode 100644
index 000000000000..e3d6b381a55c
--- /dev/null
+++ b/plugins/gradle/java/src/toml/findUsages/GradleVersionCatalogFindUsagesFactory.kt
@@ -0,0 +1,20 @@
+// 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.toml.findUsages
+
+import com.intellij.find.findUsages.FindUsagesHandler
+import com.intellij.find.findUsages.FindUsagesHandlerFactory
+import com.intellij.psi.PsiElement
+import org.toml.lang.psi.TomlKeySegment
+
+class GradleVersionCatalogFindUsagesFactory : FindUsagesHandlerFactory() {
+ override fun canFindUsages(element: PsiElement): Boolean {
+ return element is TomlKeySegment
+ }
+
+ override fun createFindUsagesHandler(element: PsiElement, forHighlightUsages: Boolean): FindUsagesHandler {
+ if (forHighlightUsages) {
+ return FindUsagesHandler.NULL_HANDLER
+ }
+ return GradleVersionCatalogFindUsagesHandler(element as TomlKeySegment)
+ }
+}
\ No newline at end of file
diff --git a/plugins/gradle/java/src/toml/findUsages/GradleVersionCatalogFindUsagesHandler.kt b/plugins/gradle/java/src/toml/findUsages/GradleVersionCatalogFindUsagesHandler.kt
new file mode 100644
index 000000000000..a8813728d16c
--- /dev/null
+++ b/plugins/gradle/java/src/toml/findUsages/GradleVersionCatalogFindUsagesHandler.kt
@@ -0,0 +1,38 @@
+// 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.toml.findUsages
+
+import com.intellij.find.findUsages.FindUsagesHandler
+import com.intellij.find.findUsages.FindUsagesOptions
+import com.intellij.openapi.actionSystem.DataContext
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.PsiElement
+import com.intellij.psi.search.GlobalSearchScope
+import org.jetbrains.plugins.gradle.codeInspection.GradleInspectionBundle
+import org.jetbrains.plugins.gradle.config.GradleBuildscriptSearchScope
+import org.toml.lang.psi.TomlKeySegment
+
+class GradleVersionCatalogFindUsagesHandler(private val tomlElement: TomlKeySegment) : FindUsagesHandler(tomlElement) {
+
+ override fun getFindUsagesOptions(dataContext: DataContext?): FindUsagesOptions {
+ val superOptions = super.getFindUsagesOptions(dataContext)
+ superOptions.searchScope = VersionCatalogSearchScope(tomlElement)
+ return superOptions
+ }
+
+ private class VersionCatalogSearchScope(context: PsiElement) : GlobalSearchScope(context.project) {
+ private val buildscriptScope = GradleBuildscriptSearchScope(context.project)
+ private val tomlScope = fileScope(context.containingFile)
+
+ override fun contains(file: VirtualFile): Boolean = buildscriptScope.contains(file) || tomlScope.contains(file)
+
+ override fun isSearchInModuleContent(aModule: Module): Boolean = buildscriptScope.isSearchInModuleContent(aModule)
+
+ override fun isSearchInLibraries(): Boolean = buildscriptScope.isSearchInLibraries
+
+ override fun getDisplayName(): String {
+ return GradleInspectionBundle.message("gradle.version.catalog.search.scope")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/gradle/java/src/toml/navigation/VersionCatalogReferenceContributor.kt b/plugins/gradle/java/src/toml/navigation/VersionCatalogReferenceContributor.kt
new file mode 100644
index 000000000000..0f082fa5c957
--- /dev/null
+++ b/plugins/gradle/java/src/toml/navigation/VersionCatalogReferenceContributor.kt
@@ -0,0 +1,67 @@
+// 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.toml.navigation
+
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.patterns.ElementPattern
+import com.intellij.patterns.PatternCondition
+import com.intellij.patterns.PlatformPatterns
+import com.intellij.patterns.PsiElementPattern
+import com.intellij.psi.*
+import com.intellij.util.ProcessingContext
+import com.intellij.util.asSafely
+import org.jetbrains.plugins.gradle.toml.getVersions
+import org.toml.lang.psi.TomlInlineTable
+import org.toml.lang.psi.TomlKeyValue
+import org.toml.lang.psi.TomlLiteral
+import org.toml.lang.psi.TomlValue
+
+class VersionCatalogReferenceContributor : PsiReferenceContributor() {
+
+ override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
+ registrar.registerReferenceProvider(versionRefPattern, VersionCatalogReferenceProvider())
+ }
+}
+
+internal val refKeyValuePattern: PsiElementPattern.Capture = PlatformPatterns
+ .psiElement(TomlKeyValue::class.java)
+ .with(RefPatternCondition())
+internal val versionRefPattern: ElementPattern = PlatformPatterns
+ .psiElement(TomlValue::class.java)
+ .withParent(refKeyValuePattern)
+
+private class RefPatternCondition : PatternCondition("'ref' key value") {
+ override fun accepts(t: TomlKeyValue, context: ProcessingContext?): Boolean {
+ val segments = t.key.segments
+ if (segments.isEmpty()) {
+ return false
+ }
+ if (segments.last().name != "ref") {
+ return false
+ }
+ if (segments.size == 1) {
+ return t.parent?.asSafely()?.parent?.asSafely()
+ ?.takeIf { it.key.segments.lastOrNull()?.name == "version" } != null
+ } else {
+ return segments.asReversed()[1].name == "version"
+ }
+ }
+
+}
+
+private class VersionCatalogReferenceProvider : PsiReferenceProvider() {
+ override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array {
+ if (element !is TomlLiteral) {
+ return emptyArray()
+ }
+ val text = StringUtil.unquoteString(element.text)
+ return arrayOf(VersionCatalogVersionReference(element, text))
+ }
+
+ private class VersionCatalogVersionReference(literal: TomlLiteral, val text: String) : PsiReferenceBase(literal) {
+ override fun resolve(): PsiElement? {
+ val versions = getVersions(element)
+ return versions.find { it.name == text }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/gradle/java/src/toml/util.kt b/plugins/gradle/java/src/toml/util.kt
new file mode 100644
index 000000000000..67b8843555b8
--- /dev/null
+++ b/plugins/gradle/java/src/toml/util.kt
@@ -0,0 +1,166 @@
+// 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.toml
+
+import com.intellij.openapi.components.service
+import com.intellij.openapi.roots.ProjectFileIndex
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.openapi.vfs.VfsUtil
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.*
+import com.intellij.psi.util.childrenOfType
+import com.intellij.util.asSafely
+import com.intellij.util.containers.tail
+import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames
+import org.jetbrains.plugins.gradle.service.resolve.VersionCatalogsLocator
+import org.jetbrains.plugins.gradle.service.resolve.getVersionCatalogFiles
+import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils
+import org.toml.lang.psi.*
+import org.toml.lang.psi.ext.name
+
+internal fun getVersions(context: PsiElement) : List {
+ val file = context.containingFile.asSafely() ?: return emptyList()
+ val targetTable = file.childrenOfType().find { it.header.key?.name == "versions" } ?: return emptyList()
+ return targetTable.childrenOfType().mapNotNull { it.key.segments.singleOrNull() }
+}
+
+
+internal fun String.getVersionCatalogParts() : List = split("_", "-")
+
+internal fun findTomlFile(context: PsiElement, name: String) : TomlFile? {
+ val file = getVersionCatalogFiles(context.project)[name] ?: return null
+ return PsiManager.getInstance(context.project).findFile(file)?.asSafely()
+}
+
+private fun findTomlFileDynamically(context: PsiElement, name: String): VirtualFile? {
+ val module = context.containingFile?.originalFile?.virtualFile?.let {
+ ProjectFileIndex.getInstance(context.project).getModuleForFile(it)
+ } ?: return null
+ val tomlPath = context.project.service().getVersionCatalogsForModule(module)[name] ?: return null
+ return VfsUtil.findFile(tomlPath, false)
+}
+
+
+
+/**
+ * @param method a method within a synthetic version catalog accessor class. The method must not return an accessor (i.e. it should be a leaf method).
+ * @param context a context element within any gradle buildscript.
+ * @return an element within TOML file, that describes [method]
+ */
+fun findOriginInTomlFile(method: PsiMethod, context: PsiElement): PsiElement? {
+ val containingClasses = mutableListOf(method.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(StringUtil.decapitalize(name), name).firstNotNullOfOrNull { findTomlFile(context, it) }
+ ?: return null
+ val tomlVisitor = TomlVersionCatalogVisitor(containingClasses.tail(), method)
+ toml.accept(tomlVisitor)
+ return tomlVisitor.resolveTarget
+}
+
+private class TomlVersionCatalogVisitor(containingClasses: List, val targetMethod: PsiMethod) : TomlRecursiveVisitor() {
+ private val containingClasses: MutableList = 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?.asSafely()?.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) {
+ val camelCasedName = getCapitalizedAccessorName(targetMethod)
+ for (tomlEntry in values) {
+ val keyName =
+ tomlEntry.key.segments.firstOrNull()?.name?.getVersionCatalogParts()?.joinToString("", transform = StringUtil::capitalize)
+ ?: continue
+ if (camelCasedName == keyName) {
+ resolveTarget = tomlEntry
+ return
+ }
+ }
+ }
+}
+
+internal fun getCapitalizedAccessorName(method: PsiMethod): String? {
+ val propertyName = GroovyPropertyUtils.getPropertyName(method) ?: return null
+ val methodFinalPart = GroovyPropertyUtils.capitalize(propertyName)
+ val methodParts = method.containingClass?.takeUnless { it.name?.startsWith(
+ LIBRARIES_FOR_PREFIX) == true }?.name?.trimAccessorName()
+ return (methodParts ?: "") + methodFinalPart
+}
+
+
+private fun String.trimAccessorName(): String {
+ for (suffix in listOf(BUNDLE_ACCESSORS_SUFFIX,
+ LIBRARY_ACCESSORS_SUFFIX,
+ PLUGIN_ACCESSORS_SUFFIX,
+ VERSION_ACCESSORS_SUFFIX)) {
+ if (endsWith(suffix)) return substringBeforeLast(suffix)
+ }
+ return this
+}
+
+
+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"
+
+
+internal const val BUNDLE_ACCESSORS_SUFFIX = "BundleAccessors"
+internal const val LIBRARY_ACCESSORS_SUFFIX = "LibraryAccessors"
+internal const val PLUGIN_ACCESSORS_SUFFIX = "PluginAccessors"
+internal const val VERSION_ACCESSORS_SUFFIX = "VersionAccessors"
+
+internal const val LIBRARIES_FOR_PREFIX = "LibrariesFor"
diff --git a/plugins/gradle/java/testSources/dsl/GradleCompletionTest.kt b/plugins/gradle/java/testSources/dsl/GradleCompletionTest.kt
index d7d6e104572e..44af28881116 100644
--- a/plugins/gradle/java/testSources/dsl/GradleCompletionTest.kt
+++ b/plugins/gradle/java/testSources/dsl/GradleCompletionTest.kt
@@ -31,7 +31,7 @@ class GradleCompletionTest : GradleCodeInsightTestCase() {
@BaseGradleVersionSource
fun testGrayOutForeignCompletionElement(gradleVersion: GradleVersion) {
testJavaProject(gradleVersion) {
- testCompletion("repositories { mavenCentral { go } }") {
+ testCompletion("build.gradle", "repositories { mavenCentral { go } }") {
var hasGoogle = false
for (element in it) {
if (element.lookupString != "google") {
diff --git a/plugins/gradle/java/testSources/dsl/versionCatalogs/GradleVersionCatalogResolveTest.kt b/plugins/gradle/java/testSources/dsl/versionCatalogs/GradleVersionCatalogResolveTest.kt
new file mode 100644
index 000000000000..7b9243ddf717
--- /dev/null
+++ b/plugins/gradle/java/testSources/dsl/versionCatalogs/GradleVersionCatalogResolveTest.kt
@@ -0,0 +1,165 @@
+// 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("libs") {
+ 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("libs2") {
+ assertInstanceOf(GroovyPsiElement::class.java, it)
+ assertTrue(it.parentOfType(true)!!.resolveMethod()!!.name == "libs2")
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @BaseGradleVersionSource
+ fun testNavigationToCustomCatalog(gradleVersion: GradleVersion) {
+ test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
+ testGotoDefinition("libs3") {
+ assertInstanceOf(GroovyPsiElement::class.java, it)
+ assertTrue(it.parentOfType(true)!!.resolveMethod()!!.name == "libs3")
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @BaseGradleVersionSource
+ fun testNavigationToTomlEntry(gradleVersion: GradleVersion) {
+ test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
+ testGotoDefinition("libs.groovy.core") {
+ 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.groovy.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.bundles") {
+ 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.baz") {
+ assertInstanceOf(GroovyPsiElement::class.java, it)
+ assertTrue(it.parentOfType(true)!!.argumentList.expressionArguments[0].text == "\"foo.bar.baz\"")
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @BaseGradleVersionSource
+ fun testNavigationToLibraryInSettings2(gradleVersion: GradleVersion) {
+ test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
+ testGotoDefinition("libs3.foo.nn.mm") {
+ assertInstanceOf(GroovyPsiElement::class.java, it)
+ assertTrue(it.parentOfType(true)!!.argumentList.expressionArguments[0].text == "\"foo-nn-mm\"")
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @BaseGradleVersionSource
+ fun testNavigationWithCapitalLetters(gradleVersion: GradleVersion) {
+ test(gradleVersion, BASE_VERSION_CATALOG_FIXTURE) {
+ testGotoDefinition("libs2.getCheck().getCapital().getLetter()") {
+ 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())
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/gradle/java/testSources/dsl/versionCatalogs/GradleVersionCatalogsFindUsagesTest.kt b/plugins/gradle/java/testSources/dsl/versionCatalogs/GradleVersionCatalogsFindUsagesTest.kt
new file mode 100644
index 000000000000..634459b6be07
--- /dev/null
+++ b/plugins/gradle/java/testSources/dsl/versionCatalogs/GradleVersionCatalogsFindUsagesTest.kt
@@ -0,0 +1,92 @@
+// 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.PsiReference
+import com.intellij.psi.search.searches.ReferencesSearch
+import com.intellij.testFramework.runInEdtAndWait
+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.GroovyFileBase
+import org.junit.jupiter.api.Assertions.assertNotNull
+import org.junit.jupiter.params.ParameterizedTest
+import org.toml.lang.psi.TomlFile
+
+class GradleVersionCatalogsFindUsagesTest : GradleCodeInsightTestCase() {
+
+ private fun testVersionCatalogFindUsages(version: GradleVersion, versionCatalogText: String, buildGradleText: String,
+ checker: (Collection) -> Unit) {
+ checkCaret(versionCatalogText)
+ test(version, BASE_VERSION_CATALOG_FIXTURE) {
+ val versionCatalog = findOrCreateFile("gradle/libs.versions.toml", versionCatalogText)
+ findOrCreateFile("build.gradle", buildGradleText)
+ runInEdtAndWait {
+ codeInsightFixture.configureFromExistingVirtualFile(versionCatalog)
+ val elementAtCaret = codeInsightFixture.elementAtCaret
+ assertNotNull(elementAtCaret)
+ val usages = ReferencesSearch.search(elementAtCaret).findAll()
+ checker(usages)
+ }
+ }
+
+ }
+
+ @ParameterizedTest
+ @BaseGradleVersionSource
+ fun testHasUsages(gradleVersion: GradleVersion) {
+ testVersionCatalogFindUsages(gradleVersion, """
+ [libraries]
+ groovy-core = "org.codehaus.groovy:groovy:2.7.3"
+ """.trimIndent(), """
+ libs.groovy.core
+ """.trimIndent()) {
+ assert(it.isNotEmpty())
+ }
+ }
+
+ @ParameterizedTest
+ @BaseGradleVersionSource
+ fun testHasNoUsages(gradleVersion: GradleVersion) {
+ testVersionCatalogFindUsages(gradleVersion, """
+ [versions]
+ foo = "4.7.6"
+ [libraries]
+ groovy-core = "org.codehaus.groovy:groovy:2.7.3"
+ aaa-bbb = { group = "org.apache.groovy", name = "groovy", version.ref = "groovy" }
+ """.trimIndent(), """
+ libs.groovy
+ """.trimIndent()) {
+ assert(it.isEmpty())
+ }
+ }
+
+ @ParameterizedTest
+ @BaseGradleVersionSource
+ fun testFindUsagesOfVersion(gradleVersion: GradleVersion) {
+ testVersionCatalogFindUsages(gradleVersion, """
+ [versions]
+ foo = "4.7.6"
+ [libraries]
+ aaa-bbb = { group = "org.apache.groovy", name = "groovy", version.ref = "foo" }
+ """.trimIndent(), """
+ libs.versions.foo
+ """.trimIndent()) { refs ->
+ assertNotNull(refs.find { it.element.containingFile is GroovyFileBase })
+ assertNotNull(refs.find { it.element.containingFile is TomlFile })
+ }
+ }
+
+
+ companion object {
+ private val BASE_VERSION_CATALOG_FIXTURE = GradleTestFixtureBuilder
+ .create("GradleVersionCatalogs-find-usages-bare") {
+ withSettingsFile {
+ setProjectName("GradleVersionCatalogs-find-usages-bare")
+ enableFeaturePreview("VERSION_CATALOGS")
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testFramework/GradleCodeInsightTestCase.kt b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testFramework/GradleCodeInsightTestCase.kt
index 60599bb9b965..4daa6a9c455e 100644
--- a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testFramework/GradleCodeInsightTestCase.kt
+++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testFramework/GradleCodeInsightTestCase.kt
@@ -30,7 +30,7 @@ abstract class GradleCodeInsightTestCase : GradleCodeInsightBaseTestCase(), Expr
}
}
- private fun checkCaret(expression: String) {
+ protected fun checkCaret(expression: String) {
assertTrue("" in expression, "Please define caret position in build script.")
}
@@ -59,20 +59,21 @@ abstract class GradleCodeInsightTestCase : GradleCodeInsightBaseTestCase(), Expr
}
}
- fun testCompletion(expression: String, checker: (Array) -> Unit) {
+ fun testCompletion(fileName: String, expression: String, checker: (Array) -> Unit) {
checkCaret(expression)
- val file = findOrCreateFile("build.gradle", expression)
+ val file = findOrCreateFile(fileName, expression)
runInEdtAndWait {
codeInsightFixture.configureFromExistingVirtualFile(file)
checker(codeInsightFixture.completeBasic())
}
}
- fun testCompletion(expression: String, vararg completionCandidates: String) = testCompletion(expression) {
+ fun testCompletion(expression: String, vararg completionCandidates: String, orderDependent: Boolean = true) = testCompletion("build.gradle", expression) {
val lookup = listOf(*it)
var startIndex = 0
for (candidate in completionCandidates) {
- val newIndex = lookup.subList(startIndex, lookup.size).indexOfFirst { it.lookupString == candidate }
+ val fromIndex = if (orderDependent) startIndex else 0
+ val newIndex = lookup.subList(fromIndex, lookup.size).indexOfFirst { it.lookupString == candidate }
assertTrue(newIndex != -1, "Element '$candidate' must be in the lookup")
startIndex = newIndex + 1
}