[kotlin] KTIJ-33536 improve pluginId gradle reference resolution

- Refactor `AbstractKotlinGradleReferenceProvider` for cleaner callable ID resolution.
- Add validation for plugin ID references in `KotlinGradlePluginReferenceProvider`.

Space-RevId: d8c74cde694f4d1a26b994021faeff84dca3c202

Merge-request: IJ-MR-188765
Merged-by: Vlad Koshkin <Vladislav.Koshkin@jetbrains.com>

GitOrigin-RevId: 2195094d9b0282ed8bc3e361e05c3fc3e5ac87c1
This commit is contained in:
Vlad Koshkin
2026-02-05 15:23:18 +00:00
committed by intellij-monorepo-bot
parent ab4a557c86
commit 6561f4fb44
6 changed files with 115 additions and 33 deletions
@@ -34,8 +34,7 @@ abstract class AbstractKotlinGradleReferenceProvider: ImplicitReferenceProvider
val callExpression = element?.getParentOfType<KtCallExpression>(true, KtDeclarationWithBody::class.java) ?: return null
return allowAnalysisOnEdt {
analyze(callExpression) {
val singleFunctionCallOrNull = callExpression.resolveToCall()?.singleFunctionCallOrNull()
singleFunctionCallOrNull?.symbol?.callableId
callExpression.resolveToCall()?.singleFunctionCallOrNull()?.symbol?.callableId
}
}
}
@@ -1,24 +1,83 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.gradleCodeInsightCommon
import com.intellij.lang.parameterInfo.ParameterInfoUtils.findParentOfType
import com.intellij.model.psi.PsiSymbolReference
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.resolution.singleFunctionCallOrNull
import org.jetbrains.kotlin.analysis.api.resolution.singleVariableAccessCall
import org.jetbrains.kotlin.analysis.api.resolution.symbol
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.plugins.gradle.service.resolve.GradlePluginReference
private val GRADLE_DSL_ID: Name = Name.identifier("id")
private val PLUGIN_DEPENDENCIES_SPEC = FqName("PluginDependenciesSpec")
private val KOTLIN_PROJECT_SCRIPT_TEMPLATE = FqName("KotlinProjectScriptTemplate")
private val PLUGINS: Name = Name.identifier("plugins")
class KotlinGradlePluginReferenceProvider : AbstractKotlinGradleReferenceProvider() {
@OptIn(KaAllowAnalysisOnEdt::class)
override fun getImplicitReference(
element: PsiElement,
offsetInElement: Int
): PsiSymbolReference? {
val text = getTextFromLiteralEntry(element.parent) ?: return null
val callableId = analyzeSurroundingCallExpression(element.parent) ?: return null
if (callableId.packageName != GRADLE_DSL_PACKAGE || callableId.callableName != GRADLE_DSL_ID) return null
val length = element.textRange.length
return GradlePluginReference(element, TextRange(0, length), text)
): PsiSymbolReference? = when (element) {
is KtNameReferenceExpression -> createFromNameReference(element)
is KtCallExpression -> createFromCall(element)
else -> null
}
@OptIn(KaAllowAnalysisOnEdt::class)
private fun createFromCall(element: KtCallExpression): GradlePluginReference? {
val maybePluginId = getSingleFunctionCallableId(element)
if (maybePluginId == null || !maybePluginId.isPluginId()) return null
val literal = PsiTreeUtil.findChildOfType(element, KtLiteralStringTemplateEntry::class.java) ?: return null
val range = TextRange(0, literal.textRange.length)
return GradlePluginReference(literal, range, literal.text)
}
@OptIn(KaAllowAnalysisOnEdt::class)
private fun createFromNameReference(element: KtNameReferenceExpression): GradlePluginReference? {
val parentCallExpression = findParentOfType(element, KtCallExpression::class.java) ?: return null
val maybePluginsSection = getSingleFunctionCallableId(parentCallExpression)
if (maybePluginsSection == null || !maybePluginsSection.isPluginsSection()) return null
val pluginCallableId = getSingleVariableCallableId(element)
if (pluginCallableId == null || pluginCallableId.packageName != GRADLE_DSL_PACKAGE) return null
val range = TextRange(0, element.textRange.length)
return GradlePluginReference(element, range, pluginCallableId.callableName.identifier)
}
@OptIn(KaAllowAnalysisOnEdt::class)
private fun getSingleFunctionCallableId(callExpression: KtCallExpression) = allowAnalysisOnEdt {
analyze(callExpression) {
callExpression.resolveToCall()?.singleFunctionCallOrNull()?.symbol?.callableId
}
}
@OptIn(KaAllowAnalysisOnEdt::class)
private fun getSingleVariableCallableId(nameReferenceExpression: KtNameReferenceExpression) = allowAnalysisOnEdt {
analyze(nameReferenceExpression) {
nameReferenceExpression.resolveToCall()?.singleVariableAccessCall()?.symbol?.callableId
}
}
private fun CallableId.isPluginsSection(): Boolean =
callableName == PLUGINS && className == KOTLIN_PROJECT_SCRIPT_TEMPLATE
private fun CallableId.isPluginId(): Boolean =
callableName == GRADLE_DSL_ID && (packageName == GRADLE_DSL_PACKAGE || className == PLUGIN_DEPENDENCIES_SPEC)
}
@@ -20,7 +20,7 @@ import org.junit.jupiter.params.ParameterizedTest
@GradleProjectTestApplication
@AssertKotlinPluginMode
@TestRoot("idea/tests/testData/")
@TestDataPath("\$CONTENT_ROOT")
@TestDataPath($$"$CONTENT_ROOT")
@TestMetadata("../../../idea/tests/testData/gradle/navigation/composite")
class K2GradleBuildLogicPluginNavigationTest : AbstractKotlinGradleNavigationTest() {
@ParameterizedTest
@@ -37,31 +37,56 @@ class K2GradleBuildLogicPluginNavigationTest : AbstractKotlinGradleNavigationTes
verifyFileShouldStayTheSame(gradleVersion)
}
@ParameterizedTest
@BaseGradleVersionSource
@TestMetadata("testNavigationToBacktickedSettingsPluginFromSettingsGradleKts.test")
fun testNavigationToBacktickedSettingsPluginFromSettingsGradleKts(gradleVersion: GradleVersion) {
verifyNavigationFromCaretToExpected(gradleVersion)
}
override val myFixture = FIXTURE_WITH_SETTINGS_PLUGIN
companion object {
private val FIXTURE_WITH_SETTINGS_PLUGIN: GradleTestFixtureBuilder =
GradleTestFixtureBuilder.create("GradleKotlinFixture") { gradleVersion ->
withSettingsFile(gradleVersion, gradleDsl = GradleDsl.KOTLIN) {
setProjectName("GradleKotlinFixture")
}
withBuildFile(gradleVersion, gradleDsl = GradleDsl.KOTLIN) {
"""
plugins {
`kotlin-dsl`
}
""".trimIndent()
withBuildFile(gradleVersion, "buildSrc", gradleDsl = GradleDsl.KOTLIN) {
withKotlinDsl()
}
withSettingsFile(gradleVersion, "custom", gradleDsl = GradleDsl.KOTLIN) {}
withBuildFile(gradleVersion, "custom", gradleDsl = GradleDsl.KOTLIN) {
"""
plugins {
`kotlin-dsl`
withFile(
"buildSrc/src/main/kotlin/my-conventions.gradle.kts", """
val fromBuildSrc = "my-conventions.gradle.kts"
""".trimIndent()
)
withBuildFile(gradleVersion, gradleDsl = GradleDsl.KOTLIN) {
withPlugin {
code("`my-conventions`")
}
""".trimIndent()
}
withFile("custom/src/main/kotlin/custom.settings.settings.gradle.kts", "")
withSettingsFile(gradleVersion, gradleDsl = GradleDsl.KOTLIN) {
addCode("""
pluginManagement {
includeBuild("custom")
}
plugins {
id("custom.settings")
}
""".trimIndent())
}
withBuildFile(gradleVersion, "custom", gradleDsl = GradleDsl.KOTLIN) {
withKotlinDsl()
}
withFile(
"custom/src/main/kotlin/custom.settings.settings.gradle.kts", """
val fromCustomSettingsPlugin = "custom.settings.settings.gradle.kts"
""".trimIndent()
)
}
}
}
@@ -0,0 +1,5 @@
// FILE: build.gradle.kts
// "EXPECTED-NAVIGATION-SUBSTRING": "val fromBuildSrc = "my-conventions.gradle.kts""
plugins {
`my-<caret>conventions`
}
@@ -1,8 +1,5 @@
// FILE: settings.gradle.kts
// "EXPECTED-NAVIGATION-SUBSTRING": "println("custom")"
// "EXPECTED-NAVIGATION-SUBSTRING": "val fromCustomSettingsPlugin = "custom.settings.settings.gradle.kts""
plugins {
id("custom<caret>.settings")
}
// FILE: custom/src/main/kotlin/custom.settings.settings.gradle.kts
println("custom")
@@ -3,6 +3,3 @@
plugins {
id("custom<caret>.settings")
}
// FILE: custom/src/main/kotlin/custom.settings.settings.gradle.kts
println("custom")