Add data modifier to a class quickfix #KT-18220 Fixed

This commit is contained in:
Toshiaki Kameyama
2017-09-19 03:19:05 +03:00
committed by Mikhail Glukhikh
parent f08e9832a6
commit 1043284afe
27 changed files with 409 additions and 1 deletions
@@ -0,0 +1,80 @@
/*
* Copyright 2010-2017 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.quickfix
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.isVisible
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
class AddDataModifierFix(element: KtClass, private val fqName: String) : AddModifierFix(element, KtTokens.DATA_KEYWORD) {
override fun getText() = "Make '$fqName' data class"
override fun getFamilyName() = text
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtModifierListOwner>? {
val element = diagnostic.psiElement as? KtExpression ?: return null
val context = element.analyze()
val callableDescriptor = if (element is KtDestructuringDeclarationEntry) {
context[BindingContext.DECLARATION_TO_DESCRIPTOR, element.parent.parent] as? CallableDescriptor
}
else {
element.getResolvedCall(context)?.resultingDescriptor
}
val constructor = callableDescriptor?.returnType?.arguments?.firstOrNull()?.type?.constructor
?: callableDescriptor?.returnType?.constructor
val classDescriptor = constructor?.declarationDescriptor as? ClassDescriptor ?: return null
val modality = classDescriptor.modality
if (modality != Modality.FINAL || classDescriptor.isInner) return null
val ctorParams = classDescriptor.constructors.firstOrNull { it.isPrimary }?.valueParameters ?: return null
if (ctorParams.isEmpty()) return null
if (!ctorParams.all {
if (it.varargElementType != null) return@all false
val property = context[BindingContext.VALUE_PARAMETER_AS_PROPERTY, it] ?: return@all false
// NB: we use element as receiver because element is a constructor call
// which is effectively used as receiver by destructuring declaration
property.isVisible(element, element, context, element.getResolutionFacade())
}) return null
val klass = DescriptorToSourceUtils.descriptorToDeclaration(classDescriptor) as? KtClass ?: return null
val fqName = DescriptorUtils.getFqName(classDescriptor).asString()
return AddDataModifierFix(klass, fqName)
}
}
}
@@ -158,7 +158,7 @@ class QuickFixRegistrar : QuickFixContributor {
FUNCTION_EXPECTED.registerFactory(InvokeImportFix)
DELEGATE_SPECIAL_FUNCTION_MISSING.registerFactory(DelegateAccessorsImportFix)
COMPONENT_FUNCTION_MISSING.registerFactory(ComponentsImportFix)
COMPONENT_FUNCTION_MISSING.registerFactory(ComponentsImportFix, AddDataModifierFix)
NO_GET_METHOD.registerFactory(ArrayAccessorImportFix)
NO_SET_METHOD.registerFactory(ArrayAccessorImportFix)
+14
View File
@@ -0,0 +1,14 @@
// "Make 'Foo' data class" "false"
// ACTION: Create extension function 'Foo.component1'
// ACTION: Create extension function 'Foo.component2'
// ACTION: Create member function 'Foo.component1'
// ACTION: Create member function 'Foo.component2'
// ACTION: Put arguments on separate lines
// ERROR: Cannot create an instance of an abstract class
// ERROR: Destructuring declaration initializer of type Foo must have a 'component1()' function
// ERROR: Destructuring declaration initializer of type Foo must have a 'component2()' function
abstract class Foo(val bar: String, val baz: Int)
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
+14
View File
@@ -0,0 +1,14 @@
// "Make 'Foo' data class" "false"
// ACTION: Create extension function 'Test.Foo.component1'
// ACTION: Create extension function 'Test.Foo.component2'
// ACTION: Create member function 'Test.Foo.component1'
// ACTION: Create member function 'Test.Foo.component2'
// ACTION: Put arguments on separate lines
// ERROR: Destructuring declaration initializer of type Test.Foo must have a 'component1()' function
// ERROR: Destructuring declaration initializer of type Test.Foo must have a 'component2()' function
class Test {
inner class Foo(val bar: String, val baz: Int)
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
}
@@ -0,0 +1,13 @@
// "Make 'Foo' data class" "false"
// ACTION: Create extension function 'Foo.component1'
// ACTION: Create extension function 'Foo.component2'
// ACTION: Create member function 'Foo.component1'
// ACTION: Create member function 'Foo.component2'
// ACTION: Put arguments on separate lines
// ERROR: Destructuring declaration initializer of type Foo must have a 'component1()' function
// ERROR: Destructuring declaration initializer of type Foo must have a 'component2()' function
class Foo(private val bar: String, var baz: Int)
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
@@ -0,0 +1,13 @@
// "Make 'Foo' data class" "false"
// ACTION: Create extension function 'Foo.component1'
// ACTION: Create extension function 'Foo.component2'
// ACTION: Create member function 'Foo.component1'
// ACTION: Create member function 'Foo.component2'
// ACTION: Put arguments on separate lines
// ERROR: Destructuring declaration initializer of type Foo must have a 'component1()' function
// ERROR: Destructuring declaration initializer of type Foo must have a 'component2()' function
class Foo(protected val bar: String, var baz: Int)
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
+12
View File
@@ -0,0 +1,12 @@
// "Make 'Foo' data class" "false"
// ACTION: Create extension function 'Foo.component1'
// ACTION: Create extension function 'Foo.component2'
// ACTION: Create member function 'Foo.component1'
// ACTION: Create member function 'Foo.component2'
// ERROR: Destructuring declaration initializer of type Foo must have a 'component1()' function
// ERROR: Destructuring declaration initializer of type Foo must have a 'component2()' function
class Foo()
fun test() {
var (bar, baz) = Foo()<caret>
}
+13
View File
@@ -0,0 +1,13 @@
// "Make 'Foo' data class" "false"
// ACTION: Create extension function 'Foo.component1'
// ACTION: Create extension function 'Foo.component2'
// ACTION: Create member function 'Foo.component1'
// ACTION: Create member function 'Foo.component2'
// ACTION: Put arguments on separate lines
// ERROR: Destructuring declaration initializer of type Foo must have a 'component1()' function
// ERROR: Destructuring declaration initializer of type Foo must have a 'component2()' function
class Foo(val bar: String, baz: Int)
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
+13
View File
@@ -0,0 +1,13 @@
// "Make 'Foo' data class" "false"
// ACTION: Create extension function 'Foo.component1'
// ACTION: Create extension function 'Foo.component2'
// ACTION: Create member function 'Foo.component1'
// ACTION: Create member function 'Foo.component2'
// ACTION: Put arguments on separate lines
// ERROR: Destructuring declaration initializer of type Foo must have a 'component1()' function
// ERROR: Destructuring declaration initializer of type Foo must have a 'component2()' function
open class Foo(val bar: String, val baz: Int)
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
+15
View File
@@ -0,0 +1,15 @@
// "Make 'Foo' data class" "false"
// ACTION: Create extension function 'Foo.component1'
// ACTION: Create extension function 'Foo.component2'
// ACTION: Create member function 'Foo.component1'
// ACTION: Create member function 'Foo.component2'
// ACTION: Put arguments on separate lines
// ERROR: Cannot access '<init>': it is private in 'Foo'
// ERROR: Destructuring declaration initializer of type Foo must have a 'component1()' function
// ERROR: Destructuring declaration initializer of type Foo must have a 'component2()' function
// ERROR: Sealed types cannot be instantiated
sealed class Foo(val bar: String, val baz: Int)
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
+6
View File
@@ -0,0 +1,6 @@
// "Make 'Foo' data class" "true"
class Foo(val bar: String, var baz: Int)
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
+6
View File
@@ -0,0 +1,6 @@
// "Make 'Foo' data class" "true"
data class Foo(val bar: String, var baz: Int)
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
+7
View File
@@ -0,0 +1,7 @@
// "Make 'Foo' data class" "true"
class Foo(val bar: String, var baz: Int)
fun test() {
val foo = Foo("A", 1)
var (bar, baz) = foo<caret>
}
+7
View File
@@ -0,0 +1,7 @@
// "Make 'Foo' data class" "true"
data class Foo(val bar: String, var baz: Int)
fun test() {
val foo = Foo("A", 1)
var (bar, baz) = foo<caret>
}
+9
View File
@@ -0,0 +1,9 @@
// "Make 'Foo' data class" "true"
// WITH_RUNTIME
class Foo(val bar: String, var baz: Int)
fun test() {
val list = listOf(Foo("A", 1))
list.forEach { (foo<caret>, bar) ->
}
}
+9
View File
@@ -0,0 +1,9 @@
// "Make 'Foo' data class" "true"
// WITH_RUNTIME
data class Foo(val bar: String, var baz: Int)
fun test() {
val list = listOf(Foo("A", 1))
list.forEach { (foo<caret>, bar) ->
}
}
+9
View File
@@ -0,0 +1,9 @@
// "Make 'Foo' data class" "true"
// WITH_RUNTIME
class Foo(val bar: String, var baz: Int)
fun test() {
val list = listOf(Foo("A", 1))
for ((foo, bar) in list<caret>) {
}
}
+9
View File
@@ -0,0 +1,9 @@
// "Make 'Foo' data class" "true"
// WITH_RUNTIME
data class Foo(val bar: String, var baz: Int)
fun test() {
val list = listOf(Foo("A", 1))
for ((foo, bar) in list<caret>) {
}
}
+6
View File
@@ -0,0 +1,6 @@
// "Make 'Foo' data class" "true"
class Foo(private val bar: String, protected var baz: Int) {
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
}
+6
View File
@@ -0,0 +1,6 @@
// "Make 'Foo' data class" "true"
data class Foo(private val bar: String, protected var baz: Int) {
fun test() {
var (bar, baz) = Foo("A", 1)
}
}
+8
View File
@@ -0,0 +1,8 @@
// "Make 'Foo' data class" "true"
class Foo(private val bar: String, protected var baz: Int) {
class A {
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
}
}
+8
View File
@@ -0,0 +1,8 @@
// "Make 'Foo' data class" "true"
data class Foo(private val bar: String, protected var baz: Int) {
class A {
fun test() {
var (bar, baz) = Foo("A", 1)
}
}
}
+6
View File
@@ -0,0 +1,6 @@
// "Make 'Foo' data class" "true"
class Foo(internal val bar: String, var baz: Int)
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
+6
View File
@@ -0,0 +1,6 @@
// "Make 'Foo' data class" "true"
data class Foo(internal val bar: String, var baz: Int)
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
+13
View File
@@ -0,0 +1,13 @@
// "Make 'Foo' data class" "false"
// ACTION: Create extension function 'Foo.component1'
// ACTION: Create extension function 'Foo.component2'
// ACTION: Create member function 'Foo.component1'
// ACTION: Create member function 'Foo.component2'
// ACTION: Put arguments on separate lines
// ERROR: Destructuring declaration initializer of type Foo must have a 'component1()' function
// ERROR: Destructuring declaration initializer of type Foo must have a 'component2()' function
class Foo(val bar: String, vararg var baz: Int)
fun test() {
var (bar, baz) = Foo("A", 1)<caret>
}
@@ -1,5 +1,6 @@
// "Create property 'address2' as constructor parameter" "false"
// ACTION: Create property 'address' as constructor parameter
// ACTION: Make 'Person' data class
// ERROR: Destructuring declaration initializer of type Person must have a 'component3()' function
// ERROR: Destructuring declaration initializer of type Person must have a 'component4()' function
data class Person(val name: String, val age: Int)
@@ -252,6 +252,111 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
@TestMetadata("idea/testData/quickfix/addDataModifier")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class AddDataModifier extends AbstractQuickFixTest {
@TestMetadata("abstract.kt")
public void testAbstract() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/abstract.kt");
doTest(fileName);
}
public void testAllFilesPresentInAddDataModifier() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/addDataModifier"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("inner.kt")
public void testInner() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/inner.kt");
doTest(fileName);
}
@TestMetadata("invisibleParameter.kt")
public void testInvisibleParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/invisibleParameter.kt");
doTest(fileName);
}
@TestMetadata("invisibleParameter2.kt")
public void testInvisibleParameter2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/invisibleParameter2.kt");
doTest(fileName);
}
@TestMetadata("noParameter.kt")
public void testNoParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/noParameter.kt");
doTest(fileName);
}
@TestMetadata("notVarVal.kt")
public void testNotVarVal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/notVarVal.kt");
doTest(fileName);
}
@TestMetadata("open.kt")
public void testOpen() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/open.kt");
doTest(fileName);
}
@TestMetadata("sealed.kt")
public void testSealed() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/sealed.kt");
doTest(fileName);
}
@TestMetadata("test1.kt")
public void testTest1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/test1.kt");
doTest(fileName);
}
@TestMetadata("test2.kt")
public void testTest2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/test2.kt");
doTest(fileName);
}
@TestMetadata("test3.kt")
public void testTest3() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/test3.kt");
doTest(fileName);
}
@TestMetadata("test4.kt")
public void testTest4() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/test4.kt");
doTest(fileName);
}
@TestMetadata("test5.kt")
public void testTest5() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/test5.kt");
doTest(fileName);
}
@TestMetadata("test6.kt")
public void testTest6() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/test6.kt");
doTest(fileName);
}
@TestMetadata("test7.kt")
public void testTest7() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/test7.kt");
doTest(fileName);
}
@TestMetadata("vararg.kt")
public void testVararg() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addDataModifier/vararg.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/quickfix/addExclExclCall")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)