[gradle][groovy] IDEA-257715 make GradleDelegatesToProvider responsible for delegate resolving in Project's Closures.

Previously, GradleProjectContributor was responsible for delegate resolving in closures of several Project methods. It was a hardcoded logic: if Closure is a parameter of method foo() from class Project, then let delegate be CLASS_NAME. The new logic implemented in GradleDelegatesToProvider is more flexible. It allows determining a delegate if a called method with Closure has overloaded method with Action<? super AnyDelegateClass>. It covers almost all the cases supported by GradleProjectContributor, and gets triggered before it. So all covered cases were removed from there, except the delegate resolving for Project#configure(...) method.

Project#configure(...) has a version with Action<? super T> parameter, but it's not possible to surely resolve T if the version of this method with Closure was called.

This commit also has tests covering considered cases for Project.

GitOrigin-RevId: e1088ad60a61868856aca4dc0b8e943bc7e04e5f
This commit is contained in:
Nikita Biriukov
2024-03-31 21:58:15 +02:00
committed by intellij-monorepo-bot
parent ff211722d2
commit d191022332
3 changed files with 71 additions and 23 deletions

View File

@@ -70,7 +70,9 @@ class GradleDelegatesToProvider : GrDelegatesToProvider {
return delegate
}
val fqClassName = delegate.canonicalText
if (fqClassName != GRADLE_API_ARTIFACT_HANDLER) {
if (fqClassName != GRADLE_API_ARTIFACT_HANDLER
&& fqClassName != GRADLE_API_PROJECT
) {
return delegate
}
val type = createType(fqClassName, expression)

View File

@@ -1,13 +1,10 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.gradle.service.resolve
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiType
import com.intellij.util.ProcessingContext
import groovy.lang.Closure
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.*
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_PROJECT
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil.createType
import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyClosurePattern
import org.jetbrains.plugins.groovy.lang.psi.patterns.groovyClosure
@@ -20,12 +17,8 @@ import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.DelegatesToInfo
class GradleProjectContributor : GradleMethodContextContributor {
companion object {
val projectClosure: GroovyClosurePattern = groovyClosure().inMethod(
psiMethod(GRADLE_API_PROJECT, "project", "configure", "subprojects", "allprojects")
psiMethod(GRADLE_API_PROJECT, "configure")
).inMethodResult(saveProjectType)
val copySpecClosure: GroovyClosurePattern = groovyClosure().inMethod(psiMethod(GRADLE_API_PROJECT, "copy", "copySpec"))
val fileTreeClosure: GroovyClosurePattern = groovyClosure().inMethod(psiMethod(GRADLE_API_PROJECT, "fileTree"))
val filesClosure: GroovyClosurePattern = groovyClosure().inMethod(psiMethod(GRADLE_API_PROJECT, "files"))
val execClosure: GroovyClosurePattern = groovyClosure().inMethod(psiMethod(GRADLE_API_PROJECT, "exec"))
}
override fun getDelegatesToInfo(closure: GrClosableBlock): DelegatesToInfo? {
@@ -34,18 +27,6 @@ class GradleProjectContributor : GradleMethodContextContributor {
val projectType = createType(GRADLE_API_PROJECT, closure)
return DelegatesToInfo(context[projectTypeKey]?.setType(projectType) ?: projectType, Closure.DELEGATE_FIRST)
}
if (copySpecClosure.accepts(closure)) {
return DelegatesToInfo(createType(GRADLE_API_FILE_COPY_SPEC, closure), Closure.DELEGATE_FIRST)
}
if (fileTreeClosure.accepts(closure)) {
return DelegatesToInfo(createType(GRADLE_API_FILE_CONFIGURABLE_FILE_TREE, closure), Closure.DELEGATE_FIRST)
}
if (filesClosure.accepts(closure)) {
return DelegatesToInfo(createType(GRADLE_API_FILE_CONFIGURABLE_FILE_COLLECTION, closure), Closure.DELEGATE_FIRST)
}
if (execClosure.accepts(closure)) {
return DelegatesToInfo(createType(GRADLE_PROCESS_EXEC_SPEC, closure), Closure.DELEGATE_FIRST)
}
return null
}
}

View File

@@ -3,8 +3,9 @@ package org.jetbrains.plugins.gradle.dsl
import com.intellij.psi.PsiMethod
import com.intellij.testFramework.assertInstanceOf
import groovy.lang.Closure.DELEGATE_FIRST
import org.gradle.util.GradleVersion
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_PROJECT
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.*
import org.jetbrains.plugins.gradle.testFramework.GradleCodeInsightTestCase
import org.jetbrains.plugins.gradle.testFramework.annotations.AllGradleVersionsSource
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall
@@ -113,4 +114,68 @@ class GradleProjectTest : GradleCodeInsightTestCase() {
}
}
}
@ParameterizedTest
@AllGradleVersionsSource("""
"project(':') {<caret>}",
"allprojects {<caret>}",
"subprojects {<caret>}",
"configure(project(':')) {<caret>}",
"configure([project(':')]) {<caret>}",
"beforeEvaluate {<caret>}",
"afterEvaluate {<caret>}"
""")
fun `resolve a delegate in Closures of methods providing Project's context`(
gradleVersion: GradleVersion,
expression: String
) {
testEmptyProject(gradleVersion) {
testBuildscript(expression) {
closureDelegateTest(GRADLE_API_PROJECT, DELEGATE_FIRST)
}
}
}
@ParameterizedTest
@AllGradleVersionsSource(PROJECT_CONTEXTS, """
"copy{<caret>}",
"copySpec{<caret>}"
""")
fun `resolve a delegate in copy and copySpec Closures`(gradleVersion: GradleVersion, decorator: String, expression: String) {
testEmptyProject(gradleVersion) {
testBuildscript(decorator, expression) {
closureDelegateTest(GRADLE_API_FILE_COPY_SPEC, DELEGATE_FIRST)
}
}
}
@ParameterizedTest
@AllGradleVersionsSource(PROJECT_CONTEXTS)
fun `resolve a delegate in fileTree Closure`(gradleVersion: GradleVersion, decorator: String) {
testEmptyProject(gradleVersion) {
testBuildscript(decorator, "fileTree('baseDir'){<caret>}") {
closureDelegateTest(GRADLE_API_FILE_CONFIGURABLE_FILE_TREE, DELEGATE_FIRST)
}
}
}
@ParameterizedTest
@AllGradleVersionsSource(PROJECT_CONTEXTS)
fun `resolve a delegate in files Closure`(gradleVersion: GradleVersion, decorator: String) {
testEmptyProject(gradleVersion) {
testBuildscript(decorator, "files('paths'){<caret>}") {
closureDelegateTest(GRADLE_API_FILE_CONFIGURABLE_FILE_COLLECTION, DELEGATE_FIRST)
}
}
}
@ParameterizedTest
@AllGradleVersionsSource(PROJECT_CONTEXTS)
fun `resolve a delegate in exec Closure`(gradleVersion: GradleVersion, decorator: String) {
testEmptyProject(gradleVersion) {
testBuildscript(decorator, "exec{<caret>}") {
closureDelegateTest(GRADLE_PROCESS_EXEC_SPEC, DELEGATE_FIRST)
}
}
}
}