diff --git a/plugins/kotlin/gradle/code-insight-common/src/org/jetbrains/kotlin/idea/gradleCodeInsightCommon/AbstractKotlinGradleReferenceProvider.kt b/plugins/kotlin/gradle/code-insight-common/src/org/jetbrains/kotlin/idea/gradleCodeInsightCommon/AbstractKotlinGradleReferenceProvider.kt index 68bb133b3c69..9d701da4ca67 100644 --- a/plugins/kotlin/gradle/code-insight-common/src/org/jetbrains/kotlin/idea/gradleCodeInsightCommon/AbstractKotlinGradleReferenceProvider.kt +++ b/plugins/kotlin/gradle/code-insight-common/src/org/jetbrains/kotlin/idea/gradleCodeInsightCommon/AbstractKotlinGradleReferenceProvider.kt @@ -34,8 +34,7 @@ abstract class AbstractKotlinGradleReferenceProvider: ImplicitReferenceProvider val callExpression = element?.getParentOfType(true, KtDeclarationWithBody::class.java) ?: return null return allowAnalysisOnEdt { analyze(callExpression) { - val singleFunctionCallOrNull = callExpression.resolveToCall()?.singleFunctionCallOrNull() - singleFunctionCallOrNull?.symbol?.callableId + callExpression.resolveToCall()?.singleFunctionCallOrNull()?.symbol?.callableId } } } diff --git a/plugins/kotlin/gradle/code-insight-common/src/org/jetbrains/kotlin/idea/gradleCodeInsightCommon/KotlinGradlePluginReferenceProvider.kt b/plugins/kotlin/gradle/code-insight-common/src/org/jetbrains/kotlin/idea/gradleCodeInsightCommon/KotlinGradlePluginReferenceProvider.kt index fd49ddd7981f..6e112251b153 100644 --- a/plugins/kotlin/gradle/code-insight-common/src/org/jetbrains/kotlin/idea/gradleCodeInsightCommon/KotlinGradlePluginReferenceProvider.kt +++ b/plugins/kotlin/gradle/code-insight-common/src/org/jetbrains/kotlin/idea/gradleCodeInsightCommon/KotlinGradlePluginReferenceProvider.kt @@ -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) } \ No newline at end of file diff --git a/plugins/kotlin/gradle/gradle-java/k2/test/org/jetbrains/kotlin/idea/k2/codeInsight/gradle/navigation/K2GradleBuildLogicPluginNavigationTest.kt b/plugins/kotlin/gradle/gradle-java/k2/test/org/jetbrains/kotlin/idea/k2/codeInsight/gradle/navigation/K2GradleBuildLogicPluginNavigationTest.kt index 1f698b7d613f..638487fe0881 100644 --- a/plugins/kotlin/gradle/gradle-java/k2/test/org/jetbrains/kotlin/idea/k2/codeInsight/gradle/navigation/K2GradleBuildLogicPluginNavigationTest.kt +++ b/plugins/kotlin/gradle/gradle-java/k2/test/org/jetbrains/kotlin/idea/k2/codeInsight/gradle/navigation/K2GradleBuildLogicPluginNavigationTest.kt @@ -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() + ) + } } } \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/gradle/navigation/composite/testNavigationToBacktickedSettingsPluginFromSettingsGradleKts.test b/plugins/kotlin/idea/tests/testData/gradle/navigation/composite/testNavigationToBacktickedSettingsPluginFromSettingsGradleKts.test new file mode 100644 index 000000000000..e919549fcf70 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/gradle/navigation/composite/testNavigationToBacktickedSettingsPluginFromSettingsGradleKts.test @@ -0,0 +1,5 @@ +// FILE: build.gradle.kts +// "EXPECTED-NAVIGATION-SUBSTRING": "val fromBuildSrc = "my-conventions.gradle.kts"" +plugins { + `my-conventions` +} diff --git a/plugins/kotlin/idea/tests/testData/gradle/navigation/composite/testNavigationToSettingsPluginFromSettingsGradleKts.test b/plugins/kotlin/idea/tests/testData/gradle/navigation/composite/testNavigationToSettingsPluginFromSettingsGradleKts.test index e67426f23a73..49c597e6fa91 100644 --- a/plugins/kotlin/idea/tests/testData/gradle/navigation/composite/testNavigationToSettingsPluginFromSettingsGradleKts.test +++ b/plugins/kotlin/idea/tests/testData/gradle/navigation/composite/testNavigationToSettingsPluginFromSettingsGradleKts.test @@ -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.settings") } - -// FILE: custom/src/main/kotlin/custom.settings.settings.gradle.kts -println("custom") diff --git a/plugins/kotlin/idea/tests/testData/gradle/navigation/composite/testNavigationToSettingsPluginNotWorkingFromBuildGradleKts.test b/plugins/kotlin/idea/tests/testData/gradle/navigation/composite/testNavigationToSettingsPluginNotWorkingFromBuildGradleKts.test index 773b43eadceb..c1220d2fe584 100644 --- a/plugins/kotlin/idea/tests/testData/gradle/navigation/composite/testNavigationToSettingsPluginNotWorkingFromBuildGradleKts.test +++ b/plugins/kotlin/idea/tests/testData/gradle/navigation/composite/testNavigationToSettingsPluginNotWorkingFromBuildGradleKts.test @@ -3,6 +3,3 @@ plugins { id("custom.settings") } - -// FILE: custom/src/main/kotlin/custom.settings.settings.gradle.kts -println("custom") \ No newline at end of file