Fix MovePropertyToConstructorIntention applicability and type rendering
#KT-17238 Fixed (cherry picked from commit 7f9d88a)
This commit is contained in:
+29
-5
@@ -28,14 +28,16 @@ import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
|
||||
import org.jetbrains.kotlin.idea.util.CommentSaver
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens.LATEINIT_KEYWORD
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.AnnotationChecker
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
@@ -57,6 +59,7 @@ class MovePropertyToConstructorIntention :
|
||||
&& element.setter == null
|
||||
&& !element.hasModifier(LATEINIT_KEYWORD)
|
||||
&& element.getStrictParentOfType<KtClassOrObject>() is KtClass
|
||||
&& (element.initializer?.isValidInConstructor() ?: true)
|
||||
|
||||
override fun applyTo(element: KtProperty, editor: Editor?) {
|
||||
val parentClass = PsiTreeUtil.getParentOfType(element, KtClass::class.java) ?: return
|
||||
@@ -94,13 +97,14 @@ class MovePropertyToConstructorIntention :
|
||||
}
|
||||
}
|
||||
else {
|
||||
val type = (element.resolveToDescriptor(BodyResolveMode.PARTIAL) as? PropertyDescriptor)?.type ?: return
|
||||
val typeText = element.typeReference?.text ?:
|
||||
(element.resolveToDescriptor(BodyResolveMode.PARTIAL) as? PropertyDescriptor)?.type?.render() ?: return
|
||||
val parameterText = buildString {
|
||||
element.modifierList?.getModifiersText()?.let(this::append)
|
||||
propertyAnnotationsText?.takeIf(String::isNotBlank)?.let { appendWithSpaceBefore(it) }
|
||||
appendWithSpaceBefore(element.valOrVarKeyword.text)
|
||||
element.name?.let { appendWithSpaceBefore(it) }
|
||||
appendWithSpaceBefore(": ${type.fqNameSafeAsString()}")
|
||||
appendWithSpaceBefore(": $typeText")
|
||||
element.initializer?.text?.let { append(" = $it") }
|
||||
}
|
||||
|
||||
@@ -133,7 +137,7 @@ class MovePropertyToConstructorIntention :
|
||||
else
|
||||
text
|
||||
|
||||
private fun KotlinType.fqNameSafeAsString() = constructor.declarationDescriptor?.fqNameSafe?.asString() ?: "<error type>"
|
||||
private fun KotlinType.render() = IdeDescriptorRenderers.SOURCE_CODE.renderType(this)
|
||||
|
||||
private fun KtModifierList.getModifiersText() = getModifiers().joinToString(separator = " ") { it.text }
|
||||
|
||||
@@ -141,4 +145,24 @@ class MovePropertyToConstructorIntention :
|
||||
node.getChildren(null).filter { it.elementType is KtModifierKeywordToken }.map { it.psi }
|
||||
|
||||
private fun StringBuilder.appendWithSpaceBefore(str: String) = append(" " + str)
|
||||
}
|
||||
|
||||
private fun KtExpression.isValidInConstructor(): Boolean {
|
||||
val containingClass = getStrictParentOfType<KtClass>() ?: return false
|
||||
var isValid = true
|
||||
this.accept(object : KtVisitorVoid() {
|
||||
override fun visitKtElement(element: KtElement) {
|
||||
element.acceptChildren(this)
|
||||
}
|
||||
|
||||
override fun visitReferenceExpression(expression: KtReferenceExpression) {
|
||||
val context = expression.analyze(BodyResolveMode.PARTIAL)
|
||||
val declarationDescriptor = expression.getResolvedCall(context)?.resultingDescriptor ?: return
|
||||
if (declarationDescriptor.containingDeclaration == containingClass.descriptor) {
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return isValid
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
class Baz
|
||||
|
||||
fun foo(a: Int, b: String, d: Baz) {
|
||||
|
||||
}
|
||||
|
||||
class TestClass {
|
||||
val <caret>prop1 = ::foo
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import kotlin.reflect.KFunction3
|
||||
|
||||
class Baz
|
||||
|
||||
fun foo(a: Int, b: String, d: Baz) {
|
||||
|
||||
}
|
||||
|
||||
class TestClass(val prop1: KFunction3<@ParameterName(name = "a") Int, @ParameterName(name = "b") String, @ParameterName(name = "d") Baz, Unit> = ::foo) {
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class TestClass {
|
||||
val <caret>prop2 = { a: Int, b: String -> }
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
class TestClass(val prop2: (Int, String) -> Unit = { a: Int, b: String -> }) {
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class TestClass {
|
||||
val <caret>prop2 = { }
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
class TestClass(val prop2: () -> Unit = { }) {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Baz
|
||||
|
||||
class TestClass {
|
||||
val <caret>c = mapOf("foo" to Baz())
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Baz
|
||||
|
||||
class TestClass(val c: Map<String, Baz> = mapOf("foo" to Baz())) {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// IS_APPLICABLE: false
|
||||
|
||||
class MySuperClass() {
|
||||
val <caret>prop5 = this::test
|
||||
|
||||
fun test() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11280,12 +11280,30 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("functionReference.kt")
|
||||
public void testFunctionReference() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/functionReference.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("getter.kt")
|
||||
public void testGetter() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/getter.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("lambda.kt")
|
||||
public void testLambda() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/lambda.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("lambdaWithoutParameters.kt")
|
||||
public void testLambdaWithoutParameters() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/lambdaWithoutParameters.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("lateinit.kt")
|
||||
public void testLateinit() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/lateinit.kt");
|
||||
@@ -11298,6 +11316,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("map.kt")
|
||||
public void testMap() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/map.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("methodReference.kt")
|
||||
public void testMethodReference() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/methodReference.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/simple.kt");
|
||||
|
||||
Reference in New Issue
Block a user