Fix MovePropertyToConstructorIntention applicability and type rendering

#KT-17238 Fixed

(cherry picked from commit 7f9d88a)
This commit is contained in:
Vyacheslav Gerasimov
2017-04-07 11:45:54 +03:00
parent d65b7a5b4b
commit 6b9242075a
11 changed files with 110 additions and 5 deletions
@@ -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
}
@@ -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 = { }
}
@@ -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");