[gradle] IDEA-325703: Allow generating setters for various gradle classes

GitOrigin-RevId: 85cea2fdb05577f4b96219e452e13cba3266d60f
This commit is contained in:
Konstantin Nisht
2023-09-07 17:25:41 +02:00
committed by intellij-monorepo-bot
parent 94ba4e6f5f
commit e2d099f419
4 changed files with 107 additions and 12 deletions

View File

@@ -89,7 +89,7 @@
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleSettingsScriptContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleScriptMembersContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleProjectExtensionContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.transformation.GradlePropertySetterMemberContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.transformation.GradleSyntheticSetterMemberContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.transformation.GradleConfigurableCallContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.transformation.GradleActionToClosureMemberContributor"/>
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleProjectMembersContributor"/>

View File

@@ -18,7 +18,7 @@ import org.jetbrains.plugins.groovy.lang.resolve.shouldProcessProperties
* For each getter with type [org.gradle.api.provider.Property], Gradle generates a corresponding setter
* @see org.gradle.internal.instantiation.generator.AbstractClassGenerator
*/
class GradlePropertySetterMemberContributor : NonCodeMembersContributor() {
class GradleSyntheticSetterMemberContributor : NonCodeMembersContributor() {
override fun getClassNames(): Collection<String> = emptyList()
override fun getParentClassName(): String? = null
@@ -49,23 +49,48 @@ class GradlePropertySetterMemberContributor : NonCodeMembersContributor() {
}
}
/**
* Hardcoded set of classes for which there is a synthetic setter generated
*/
private val assignmentPermittedClasses = listOf(
GradleCommonClassNames.GRADLE_API_PROVIDER_PROPERTY,
GradleCommonClassNames.GRADLE_API_FILE_CONFIGURABLE_FILE_COLLECTION,
GradleCommonClassNames.GRADLE_API_PROVIDER_MAP_PROPERTY,
GradleCommonClassNames.GRADLE_API_PROVIDER_HAS_MULTIPLE_VALUES,
)
private fun processSetter(method: PsiMethod, simpleName : String, processor: PsiScopeProcessor, state: ResolveState) {
if (!isSimplePropertyGetter(method)) {
return
}
val returnType = method.returnType.asSafely<PsiClassType>() ?: return
if (returnType.resolve()?.qualifiedName != GradleCommonClassNames.GRADLE_API_PROVIDER_PROPERTY) {
val resolvedResult = returnType.resolveGenerics()
val resolvedClass = resolvedResult.element ?: return
if (resolvedClass.qualifiedName !in assignmentPermittedClasses && !hasGeneratedAssignmentInKotlin(resolvedClass)) {
return
}
val setterName = getAccessorName(simpleName, PropertyKind.SETTER)
val setter = with(LightMethodBuilder(method.manager, setterName)) {
navigationElement = method
containingClass = method.containingClass
originInfo = "Generated by decoration of Gradle property getter"
setMethodReturnType(PsiTypes.voidType())
val innerParameter = returnType.parameters.singleOrNull() ?: return
addParameter("value", innerParameter)
this
val backingSetters = resolvedClass.findMethodsAndTheirSubstitutorsByName("set", true) + resolvedClass.findMethodsAndTheirSubstitutorsByName("setFrom", true)
for (backingSetter in backingSetters) {
val generatedSetter = generateSetter(backingSetter.first, resolvedResult.substitutor, backingSetter.second, method, setterName)
processor.execute(generatedSetter, state)
}
}
private fun generateSetter(backingMethod: PsiMethod, deepSubstitutor: PsiSubstitutor, substitutor: PsiSubstitutor, originMethod: PsiMethod, setterName: String) : PsiMethod = LightMethodBuilder(backingMethod.manager, setterName).apply {
navigationElement = originMethod
containingClass = originMethod.containingClass
originInfo = "Generated by decoration of Gradle property-like class"
setMethodReturnType(PsiTypes.voidType())
for (backingParameter in backingMethod.parameterList.parameters) {
addParameter(backingParameter.name, deepSubstitutor.substitute(substitutor.substitute(backingParameter.type)))
}
}
private fun hasGeneratedAssignmentInKotlin(clazz : PsiClass) : Boolean {
val superClasses = arrayOf(clazz) + clazz.supers
return superClasses.any {superClass ->
superClass.annotations.any { it.hasQualifiedName(GradleCommonClassNames.GRADLE_API_SUPPORTS_KOTLIN_ASSIGNMENT_OVERLOADING) }
}
processor.execute(setter, state)
}

View File

@@ -6,9 +6,12 @@ import com.intellij.psi.PsiMethod
import com.intellij.testFramework.assertInstanceOf
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.api.statements.expressions.GrMethodCall
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.params.ParameterizedTest
@@ -61,4 +64,68 @@ class GradleResolveTest: GradleCodeInsightTestCase() {
}
}
}
@ParameterizedTest
@BaseGradleVersionSource
fun testGradleGeneratedSetters(gradleVersion: GradleVersion) {
test(gradleVersion, BUILD_SRC_FIXTURE) {
testBuildscript("""
tasks.register("myTask", MyTask) {
myFirstPro<caret>perty = "value" // ok
}
""".trimIndent()) {
val expression = elementUnderCaret(GrReferenceExpression::class.java)
val results = expression.multiResolve(false)
assertEquals(1, results.size)
val method = assertInstanceOf<PsiMethod>(results[0].element)
assertEquals("setMyFirstProperty", method.name)
assertEquals("getMyFirstProperty", (method.navigationElement as PsiMethod).name)
}
}
}
@ParameterizedTest
@BaseGradleVersionSource
fun testGradleGeneratedSetters2(gradleVersion: GradleVersion) {
test(gradleVersion, BUILD_SRC_FIXTURE) {
testBuildscript("""
tasks.register("myTask", MyTask) {
myCollec<caret>tion = files("hello")
}
""".trimIndent()) {
val expression = elementUnderCaret(GrReferenceExpression::class.java)
val results = expression.multiResolve(false)
assertEquals(1, results.size)
val method = assertInstanceOf<PsiMethod>(results[0].element)
assertEquals("setMyCollection", method.name)
assertEquals("getMyCollection", (method.navigationElement as PsiMethod).name)
}
}
}
companion object {
private val BUILD_SRC_FIXTURE = GradleTestFixtureBuilder.create("GradleResolveTest-buildSrc") {
withSettingsFile {
setProjectName("GradleResolveTest-buildSrc")
}
withFile("buildSrc/src/main/java/MyTask.java", """
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
public abstract class MyTask extends DefaultTask {
@Input
public abstract Property<String> getMyFirstProperty();
@InputFiles
public abstract ConfigurableFileCollection getMyCollection();
}
""".trimIndent())
}
}
}

View File

@@ -87,9 +87,12 @@ public final class GradleCommonClassNames {
@NonNls public static final String GRADLE_API_EXTRA_PROPERTIES_EXTENSION = "org.gradle.api.plugins.ExtraPropertiesExtension";
@NonNls public static final String GRADLE_PROCESS_EXEC_SPEC = "org.gradle.process.ExecSpec";
@NonNls public static final String GRADLE_API_PROVIDER_PROPERTY = "org.gradle.api.provider.Property";
@NonNls public static final String GRADLE_API_PROVIDER_MAP_PROPERTY = "org.gradle.api.provider.MapProperty";
@NonNls public static final String GRADLE_API_PROVIDER_HAS_MULTIPLE_VALUES = "org.gradle.api.provider.HasMultipleValues";
@NonNls public static final String GRADLE_API_PROVIDER_PROVIDER = "org.gradle.api.provider.Provider";
@NonNls public static final String GRADLE_API_PROVIDER_PROVIDER_CONVERTIBLE = "org.gradle.api.provider.ProviderConvertible";
@NonNls public static final String GRADLE_PLUGIN_USE_PLUGIN_DEPENDENCY = "org.gradle.plugin.use.PluginDependency";
@NonNls public static final String GRADLE_API_SUPPORTS_KOTLIN_ASSIGNMENT_OVERLOADING = "org.gradle.api.SupportsKotlinAssignmentOverloading";
private GradleCommonClassNames() {
}