KT-9839: intention introduced: convert primary constructor to secondary one

(cherry picked from commit 93aaa48)
This commit is contained in:
Mikhail Glukhikh
2016-09-26 15:18:01 +03:00
committed by Mikhail Glukhikh
parent d4418d5686
commit 7f50e6e70e
39 changed files with 492 additions and 10 deletions
@@ -0,0 +1,9 @@
class Foo {
val z = y
val x: Int
constructor(x: Int = 4, y: Int) {
this.x = x
}
}
@@ -0,0 +1,3 @@
class Foo(val x: Int = 4, y: Int) {
val z = y
}
@@ -0,0 +1,5 @@
<html>
<body>
This intention converts primary constructor to secondary one.
</body>
</html>
+5
View File
@@ -1372,6 +1372,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ConvertPrimaryConstructorToSecondaryIntention</className>
<category>Kotlin</category>
</intentionAction>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
displayName="Object literal can be converted to lambda"
groupName="Kotlin"
@@ -0,0 +1,110 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.lexer.KtTokens.THIS_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.VARARG_KEYWORD
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder
import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder.Target.CONSTRUCTOR
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
class ConvertPrimaryConstructorToSecondaryIntention : SelfTargetingIntention<KtPrimaryConstructor>(
KtPrimaryConstructor::class.java,
"Convert to secondary constructor"
) {
override fun isApplicableTo(element: KtPrimaryConstructor, caretOffset: Int) =
element.containingClassOrObject is KtClass && element.valueParameters.all { !it.hasValOrVar() || it.annotationEntries.isEmpty() }
override fun applyTo(element: KtPrimaryConstructor, editor: Editor?) {
val klass = element.containingClassOrObject as? KtClass ?: return
val factory = KtPsiFactory(klass)
val initializerMap = mutableMapOf<KtProperty, String>()
for (property in klass.getProperties()) {
if (property.typeReference == null) {
SpecifyTypeExplicitlyIntention().applyTo(property, editor)
}
val initializer = property.initializer ?: continue
initializerMap[property] = initializer.text
initializer.delete()
property.equalsToken!!.delete()
}
val constructor = factory.createSecondaryConstructor(
CallableBuilder(CONSTRUCTOR).apply {
element.modifierList?.let { modifier(it.text) }
typeParams()
name()
for (valueParameter in element.valueParameters) {
val annotations = valueParameter.annotationEntries.joinToString(separator = " ") { it.text }
val vararg = if (valueParameter.isVarArg) VARARG_KEYWORD.value else ""
param("$annotations $vararg ${valueParameter.name!!}",
valueParameter.typeReference!!.text, valueParameter.defaultValue?.text)
}
noReturnType()
for (superTypeEntry in klass.getSuperTypeListEntries()) {
if (superTypeEntry is KtSuperTypeCallEntry) {
superDelegation(superTypeEntry.valueArgumentList?.text ?: "")
superTypeEntry.replace(factory.createSuperTypeEntry(superTypeEntry.typeReference!!.text))
}
}
if (element.valueParameters.firstOrNull { it.hasValOrVar() } != null || initializerMap.isNotEmpty()) {
val valueParameterInitializers = element.valueParameters.filter { it.hasValOrVar() }.joinToString(separator = "\n") {
val name = it.name!!
"this.$name = $name"
}
val classBodyInitializers = klass.declarations.filter {
(it is KtProperty && initializerMap[it] != null) || it is KtAnonymousInitializer
}.joinToString(separator = "\n") {
if (it is KtProperty) {
val name = it.name!!
val text = initializerMap[it]
if (text != null) {
"${THIS_KEYWORD.value}.$name = $text"
}
else {
""
}
}
else {
((it as KtAnonymousInitializer).body as? KtBlockExpression)?.statements?.joinToString(separator = "\n") {
it.text
} ?: ""
}
}
blockBody(listOf(valueParameterInitializers, classBodyInitializers)
.filter(String::isNotEmpty).joinToString(separator = "\n"))
}
}.asString()
)
klass.addDeclarationBefore(constructor, null)
for (valueParameter in element.valueParameters.reversed()) {
if (!valueParameter.hasValOrVar()) continue
val isVararg = valueParameter.hasModifier(VARARG_KEYWORD)
valueParameter.removeModifier(VARARG_KEYWORD)
val typeText = valueParameter.typeReference?.text
val property = factory.createProperty(valueParameter.modifierList?.text, valueParameter.name!!,
if (isVararg && typeText != null) "Array<out $typeText>" else typeText,
valueParameter.isMutable, null)
klass.addDeclarationBefore(property, null)
}
for (anonymousInitializer in klass.getAnonymousInitializers()) {
anonymousInitializer.delete()
}
element.delete()
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.ConvertPrimaryConstructorToSecondaryIntention
@@ -0,0 +1,3 @@
annotation class Ann(val t: String)
class Annotated @Ann("42") constructor<caret>()
@@ -0,0 +1,5 @@
annotation class Ann(val t: String)
class Annotated {
@Ann("42") constructor()
}
@@ -0,0 +1,5 @@
annotation class Ann
class AnnotatedParam<caret>(@Ann x: Double) {
val y = x
}
@@ -0,0 +1,9 @@
annotation class Ann
class AnnotatedParam {
constructor(@Ann x: Double) {
this.y = x
}
val y: Double
}
@@ -0,0 +1,5 @@
// IS_APPLICABLE: false
annotation class Ann
class AnnotatedParam<caret>(val v: Double, @Ann val x: Int, var s: String)
@@ -0,0 +1 @@
class DefaultValueChain(val<caret> x1: Int, x2: Int = x1, val x3: Int = x2, x4: Int = x3, val x5: Int = x4)
@@ -0,0 +1,11 @@
class DefaultValueChain {
val x1: Int
val x3: Int
val x5: Int
constructor(x1: Int, x2: Int = x1, x3: Int = x2, x4: Int = x3, x5: Int = x4) {
this.x1 = x1
this.x3 = x3
this.x5 = x5
}
}
@@ -0,0 +1,13 @@
class InitAndParams<caret>(x: Int, z: Int) {
val y = x
val w: Int
init {
w = foo(y)
}
fun foo(arg: Int) = arg
val v = w + z
}
@@ -0,0 +1,15 @@
class InitAndParams {
constructor(x: Int, z: Int) {
this.y = x
w = foo(y)
this.v = w + z
}
val y: Int
val w: Int
fun foo(arg: Int) = arg
val v: Int
}
@@ -0,0 +1,7 @@
class ParamAndProperties<caret>(x: Int, val y: Int, z: Int, val w: Int) {
val r = x + z
val s = r + y
val t = s + w
}
@@ -0,0 +1,18 @@
class ParamAndProperties {
val y: Int
val w: Int
constructor(x: Int, y: Int, z: Int, w: Int) {
this.y = y
this.w = w
this.r = x + z
this.s = r + y
this.t = s + w
}
val r: Int
val s: Int
val t: Int
}
@@ -0,0 +1 @@
class Protected protected<caret> constructor(internal var s: String)
@@ -0,0 +1,7 @@
class Protected {
internal var s: String
protected constructor(s: String) {
this.s = s
}
}
@@ -0,0 +1 @@
class My<caret>()
@@ -0,0 +1,3 @@
class My {
constructor()
}
@@ -0,0 +1,3 @@
class UseParam<caret>(x: Int) {
val y = x
}
@@ -0,0 +1,7 @@
class UseParam {
constructor(x: Int) {
this.y = x
}
val y: Int
}
@@ -0,0 +1,5 @@
class UseParam<caret>(x: Int) {
val y = x
val z = 2 * y
}
@@ -0,0 +1,10 @@
class UseParam {
constructor(x: Int) {
this.y = x
this.z = 2 * y
}
val y: Int
val z: Int
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
class VarArg(<caret>vararg x: String) {
val strList = x.toList()
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
class VarArg {
constructor(vararg x: String) {
this.strList = x.toList()
}
val strList: List<String>
}
@@ -0,0 +1,3 @@
// WITH_RUNTIME
class VarargVal<caret>(vararg val param: String)
@@ -0,0 +1,9 @@
// WITH_RUNTIME
class VarargVal {
val param: Array<out String>
constructor(vararg param: String) {
this.param = param
}
}
@@ -0,0 +1,5 @@
abstract class Base(val x: Int)
class Derived<caret>(x: Int) : Base(x) {
val y = 2 * x
}
@@ -0,0 +1,9 @@
abstract class Base(val x: Int)
class Derived : Base {
constructor(x: Int) : super(x) {
this.y = 2 * x
}
val y: Int
}
@@ -0,0 +1,15 @@
interface A {
val s: String
}
interface B {
val x: Int
}
abstract class C(open val d: Double)
class D(<caret>open val y: Int, override val d: Double) : A, C(d), B {
override val s = "$y -> $d"
override val x = y * y
}
@@ -0,0 +1,25 @@
interface A {
val s: String
}
interface B {
val x: Int
}
abstract class C(open val d: Double)
class D : A, C, B {
open val y: Int
override val d: Double
constructor(y: Int, d: Double) : super(d) {
this.y = y
this.d = d
this.s = "$y -> $d"
this.x = y * y
}
override val s: String
override val x: Int
}
@@ -0,0 +1 @@
class WithProperties<caret>(val x: Int, val y: Int = 7, private val z: Int = 13)
@@ -0,0 +1,11 @@
class WithProperties {
val x: Int
val y: Int
private val z: Int
constructor(x: Int, y: Int = 7, z: Int = 13) {
this.x = x
this.y = y
this.z = z
}
}
@@ -1,5 +1,6 @@
// "class org.jetbrains.kotlin.idea.quickfix.AddTypeAnnotationToValueParameterFix" "false"
// ERROR: A type annotation is required on a value parameter
// ACTION: Create test
// ACTION: Convert to secondary constructor
class Foo(val bar<caret>)
@@ -3,5 +3,6 @@
// ACTION: Make private
// ACTION: Make protected
// ACTION: Create test
// ACTION: Convert to secondary constructor
class C(<caret>val x: String) {
}
@@ -4358,6 +4358,105 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ConvertPrimaryConstructorToSecondary extends AbstractIntentionTest {
public void testAllFilesPresentInConvertPrimaryConstructorToSecondary() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertPrimaryConstructorToSecondary"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
@TestMetadata("annotatedConstructor.kt")
public void testAnnotatedConstructor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/annotatedConstructor.kt");
doTest(fileName);
}
@TestMetadata("annotatedParam.kt")
public void testAnnotatedParam() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/annotatedParam.kt");
doTest(fileName);
}
@TestMetadata("annotatedProperty.kt")
public void testAnnotatedProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/annotatedProperty.kt");
doTest(fileName);
}
@TestMetadata("defaultValueChain.kt")
public void testDefaultValueChain() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/defaultValueChain.kt");
doTest(fileName);
}
@TestMetadata("initAndParams.kt")
public void testInitAndParams() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/initAndParams.kt");
doTest(fileName);
}
@TestMetadata("paramsAndProperties.kt")
public void testParamsAndProperties() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/paramsAndProperties.kt");
doTest(fileName);
}
@TestMetadata("protectedConstructor.kt")
public void testProtectedConstructor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/protectedConstructor.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/simple.kt");
doTest(fileName);
}
@TestMetadata("useParam.kt")
public void testUseParam() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/useParam.kt");
doTest(fileName);
}
@TestMetadata("useParamChain.kt")
public void testUseParamChain() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/useParamChain.kt");
doTest(fileName);
}
@TestMetadata("vararg.kt")
public void testVararg() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/vararg.kt");
doTest(fileName);
}
@TestMetadata("varargVal.kt")
public void testVarargVal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/varargVal.kt");
doTest(fileName);
}
@TestMetadata("withBaseClass.kt")
public void testWithBaseClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/withBaseClass.kt");
doTest(fileName);
}
@TestMetadata("withMultipleInheritance.kt")
public void testWithMultipleInheritance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/withMultipleInheritance.kt");
doTest(fileName);
}
@TestMetadata("withProperties.kt")
public void testWithProperties() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/withProperties.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/convertPropertyInitializerToGetter")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)