[gradle] Enable find usages and references in version catalog toml files

GitOrigin-RevId: 093c10b45b965b1167a1d766a7f67c2c2ac1e22b
This commit is contained in:
Konstantin Nisht
2022-11-24 22:43:08 +03:00
committed by intellij-monorepo-bot
parent 496e6115ec
commit 019151b7ad
12 changed files with 581 additions and 17 deletions

View File

@@ -30,6 +30,7 @@
<orderEntry type="module" module-name="intellij.platform.jps.model.impl" scope="TEST" />
<orderEntry type="module" module-name="intellij.properties.psi" scope="TEST" />
<orderEntry type="module" module-name="intellij.toml" scope="TEST" />
<orderEntry type="module" module-name="intellij.gradle.analysis" scope="TEST" />
<orderEntry type="library" scope="TEST" name="kotlinx-coroutines-jdk8" level="project" />
</component>
</module>

View File

@@ -2,9 +2,9 @@
<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"/>-->
<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"/>-->

View File

@@ -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")
}
}

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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")
}
}
}

View File

@@ -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<TomlKeyValue> = PlatformPatterns
.psiElement(TomlKeyValue::class.java)
.with(RefPatternCondition())
internal val versionRefPattern: ElementPattern<TomlValue> = PlatformPatterns
.psiElement(TomlValue::class.java)
.withParent(refKeyValuePattern)
private class RefPatternCondition : PatternCondition<TomlKeyValue>("'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<TomlInlineTable>()?.parent?.asSafely<TomlKeyValue>()
?.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<PsiReference> {
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<TomlLiteral>(literal) {
override fun resolve(): PsiElement? {
val versions = getVersions(element)
return versions.find { it.name == text }
}
}
}

View File

@@ -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<TomlKeySegment> {
val file = context.containingFile.asSafely<TomlFile>() ?: return emptyList()
val targetTable = file.childrenOfType<TomlTable>().find { it.header.key?.name == "versions" } ?: return emptyList()
return targetTable.childrenOfType<TomlKeyValue>().mapNotNull { it.key.segments.singleOrNull() }
}
internal fun String.getVersionCatalogParts() : List<String> = 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<TomlFile>()
}
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<VersionCatalogsLocator>().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<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?.asSafely<PsiClassType>()?.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?.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"

View File

@@ -31,7 +31,7 @@ class GradleCompletionTest : GradleCodeInsightTestCase() {
@BaseGradleVersionSource
fun testGrayOutForeignCompletionElement(gradleVersion: GradleVersion) {
testJavaProject(gradleVersion) {
testCompletion("repositories { mavenCentral { go<caret> } }") {
testCompletion("build.gradle", "repositories { mavenCentral { go<caret> } }") {
var hasGoogle = false
for (element in it) {
if (element.lookupString != "google") {

View File

@@ -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("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

@@ -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<PsiReference>) -> 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]
groov<caret>y-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]
groov<caret>y-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]
fo<caret>o = "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")
}
}
}
}

View File

@@ -30,7 +30,7 @@ abstract class GradleCodeInsightTestCase : GradleCodeInsightBaseTestCase(), Expr
}
}
private fun checkCaret(expression: String) {
protected fun checkCaret(expression: String) {
assertTrue("<caret>" in expression, "Please define caret position in build script.")
}
@@ -59,20 +59,21 @@ abstract class GradleCodeInsightTestCase : GradleCodeInsightBaseTestCase(), Expr
}
}
fun testCompletion(expression: String, checker: (Array<LookupElement>) -> Unit) {
fun testCompletion(fileName: String, expression: String, checker: (Array<LookupElement>) -> 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
}