From 64f6ed586b6e8e836c98969a84d917e9ba576df7 Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Tue, 4 Feb 2020 18:54:25 +0900 Subject: [PATCH] "Convert to primary constructor" inspection: do not report when property has setter #KT-22142 Fixed --- ...tSecondaryConstructorToPrimaryIntention.kt | 55 ++++++++++++------- .../convertSecondaryToPrimary/test.kt | 13 +++++ 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertSecondaryConstructorToPrimaryIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertSecondaryConstructorToPrimaryIntention.kt index dec834668d3..47244047193 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertSecondaryConstructorToPrimaryIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertSecondaryConstructorToPrimaryIntention.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection +import org.jetbrains.kotlin.idea.intentions.ConvertSecondaryConstructorToPrimaryIntention.Companion.tryConvertToPropertyByParameterInitialization import org.jetbrains.kotlin.idea.util.CommentSaver import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* @@ -25,7 +26,18 @@ import org.jetbrains.kotlin.util.kind class ConvertSecondaryConstructorToPrimaryInspection : IntentionBasedInspection( ConvertSecondaryConstructorToPrimaryIntention::class, - { constructor -> constructor.containingClass()?.secondaryConstructors?.size == 1 } + fun(constructor: KtSecondaryConstructor): Boolean { + val containingClass = constructor.containingClass() ?: return false + if (containingClass.secondaryConstructors.size != 1) return false + val context = containingClass.analyzeWithContent() + val constructorDescriptor = context[BindingContext.CONSTRUCTOR, constructor] ?: return false + for (statement in constructor.bodyExpression?.statements.orEmpty()) { + val propertyDescriptor = statement.tryConvertToPropertyByParameterInitialization(constructorDescriptor, context)?.second + val property = propertyDescriptor?.let { DescriptorToSourceUtils.descriptorToDeclaration(it) as? KtProperty } + if (property?.setter?.hasBody() == true) return false + } + return true + } ) { override fun inspectionTarget(element: KtSecondaryConstructor) = element.getConstructorKeyword() } @@ -62,26 +74,6 @@ class ConvertSecondaryConstructorToPrimaryIntention : SelfTargetingRangeIntentio return TextRange(element.startOffset, element.valueParameterList?.endOffset ?: element.getConstructorKeyword().endOffset) } - private fun KtExpression.tryConvertToPropertyByParameterInitialization( - constructorDescriptor: ConstructorDescriptor, context: BindingContext - ): Pair? { - if (this !is KtBinaryExpression || operationToken != KtTokens.EQ) return null - val rightReference = right as? KtReferenceExpression ?: return null - val rightDescriptor = context[BindingContext.REFERENCE_TARGET, rightReference] as? ValueParameterDescriptor ?: return null - if (rightDescriptor.containingDeclaration != constructorDescriptor) return null - val leftReference = when (val left = left) { - is KtReferenceExpression -> - left - is KtDotQualifiedExpression -> - if (left.receiverExpression is KtThisExpression) left.selectorExpression as? KtReferenceExpression else null - else -> - null - } - - val leftDescriptor = context[BindingContext.REFERENCE_TARGET, leftReference] as? PropertyDescriptor ?: return null - return rightDescriptor to leftDescriptor - } - private fun KtSecondaryConstructor.extractInitializer( parameterToPropertyMap: MutableMap, context: BindingContext, @@ -189,4 +181,25 @@ class ConvertSecondaryConstructorToPrimaryIntention : SelfTargetingRangeIntentio element.delete() } } + + companion object { + fun KtExpression.tryConvertToPropertyByParameterInitialization( + constructorDescriptor: ConstructorDescriptor, context: BindingContext + ): Pair? { + if (this !is KtBinaryExpression || operationToken != KtTokens.EQ) return null + val rightReference = right as? KtReferenceExpression ?: return null + val rightDescriptor = context[BindingContext.REFERENCE_TARGET, rightReference] as? ValueParameterDescriptor ?: return null + if (rightDescriptor.containingDeclaration != constructorDescriptor) return null + val leftReference = when (val left = left) { + is KtReferenceExpression -> + left + is KtDotQualifiedExpression -> + if (left.receiverExpression is KtThisExpression) left.selectorExpression as? KtReferenceExpression else null + else -> + null + } + val leftDescriptor = context[BindingContext.REFERENCE_TARGET, leftReference] as? PropertyDescriptor ?: return null + return rightDescriptor to leftDescriptor + } + } } \ No newline at end of file diff --git a/idea/testData/inspections/convertSecondaryToPrimary/test.kt b/idea/testData/inspections/convertSecondaryToPrimary/test.kt index 89c04a930fc..4093f2a917c 100644 --- a/idea/testData/inspections/convertSecondaryToPrimary/test.kt +++ b/idea/testData/inspections/convertSecondaryToPrimary/test.kt @@ -16,4 +16,17 @@ class NotSingle { constructor(x: Int) { this.x = x } +} + +// KT-22142 +class Person { + constructor(email: String) { + this.email = email + } + + var email: String + set(value) { + require(value.isNotBlank(), { "The email cannot be blank" }) + field = value + } } \ No newline at end of file