Introduce "add missing actual members" quick-fix #KT-18449 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
1b0421d27b
commit
739b21f519
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeParameter.Cr
|
||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateLocalVariableActionFactory
|
||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByNamedArgumentActionFactory
|
||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByRefActionFactory
|
||||
import org.jetbrains.kotlin.idea.quickfix.expectactual.AddActualFix
|
||||
import org.jetbrains.kotlin.idea.quickfix.expectactual.CreateActualFix
|
||||
import org.jetbrains.kotlin.idea.quickfix.migration.MigrateExternalExtensionFix
|
||||
import org.jetbrains.kotlin.idea.quickfix.migration.MigrateTypeParameterListFix
|
||||
@@ -480,6 +481,8 @@ class QuickFixRegistrar : QuickFixContributor {
|
||||
OVERLOADS_WITHOUT_DEFAULT_ARGUMENTS.registerFactory(RemoveAnnotationFix.JvmOverloads)
|
||||
|
||||
NO_ACTUAL_FOR_EXPECT.registerFactory(CreateActualFix)
|
||||
NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS.registerFactory(AddActualFix)
|
||||
|
||||
ACTUAL_MISSING.registerFactory(AddModifierFix.createFactory(KtTokens.ACTUAL_KEYWORD))
|
||||
|
||||
CAST_NEVER_SUCCEEDS.registerFactory(ReplacePrimitiveCastWithNumberConversionFix)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.expectactual
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
|
||||
class AddActualFix(
|
||||
actualClassOrObject: KtClassOrObject,
|
||||
expectedClassOrObject: KtClassOrObject
|
||||
) : KotlinQuickFixAction<KtClassOrObject>(actualClassOrObject) {
|
||||
|
||||
private val expectedClassPointer = expectedClassOrObject.createSmartPointer()
|
||||
|
||||
override fun getFamilyName() = text
|
||||
|
||||
override fun getText() = "Add missing actual members"
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
val element = element ?: return
|
||||
val expectedClass = expectedClassPointer.element ?: return
|
||||
val factory = KtPsiFactory(element)
|
||||
val pureActualClass = factory.generateClassOrObjectByExpectedClass(
|
||||
project, expectedClass, actualNeeded = true, existingDeclarations = element.declarations
|
||||
)
|
||||
for (declaration in pureActualClass.declarations) {
|
||||
element.addDeclaration(declaration)
|
||||
}
|
||||
val primaryConstructor = pureActualClass.primaryConstructor
|
||||
if (element.primaryConstructor == null && primaryConstructor != null) {
|
||||
element.addAfter(primaryConstructor, element.nameIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val incompatibleMap = DiagnosticFactory.cast(diagnostic, Errors.NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS).b
|
||||
val expectedClassDescriptor = incompatibleMap.firstOrNull()?.first?.containingDeclaration as? ClassDescriptor
|
||||
?: return null
|
||||
val expectedClassOrObject = DescriptorToSourceUtils.descriptorToDeclaration(expectedClassDescriptor) as? KtClassOrObject
|
||||
?: return null
|
||||
return (diagnostic.psiElement as? KtClassOrObject)?.let { AddActualFix(it, expectedClassOrObject) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ class CreateActualClassFix(
|
||||
klass: KtClassOrObject,
|
||||
actualPlatform: MultiTargetPlatform.Specific
|
||||
) : CreateActualFix<KtClassOrObject>(klass, actualPlatform, { project, element ->
|
||||
generateClassOrObject(project, element, actualNeeded = true)
|
||||
generateClassOrObjectByExpectedClass(project, element, actualNeeded = true)
|
||||
}) {
|
||||
|
||||
override val elementType = run {
|
||||
@@ -206,15 +206,36 @@ private fun KtModifierListOwner.replaceExpectModifier(actualNeeded: Boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtPsiFactory.generateClassOrObject(
|
||||
internal fun KtPsiFactory.generateClassOrObjectByExpectedClass(
|
||||
project: Project,
|
||||
expectedClass: KtClassOrObject,
|
||||
actualNeeded: Boolean
|
||||
actualNeeded: Boolean,
|
||||
existingDeclarations: List<KtDeclaration> = emptyList()
|
||||
): KtClassOrObject {
|
||||
fun KtDeclaration.exists() =
|
||||
existingDeclarations.any {
|
||||
name == it.name && this.javaClass == it.javaClass && when (this) {
|
||||
is KtClassOrObject, is KtProperty, is KtEnumEntry -> true
|
||||
is KtFunction -> {
|
||||
it as KtFunction
|
||||
valueParameters.size == it.valueParameters.size &&
|
||||
valueParameters.zip(it.valueParameters).all { (parameter, existingParameter) ->
|
||||
parameter.name == existingParameter.name &&
|
||||
parameter.typeReference?.text == existingParameter.typeReference?.text
|
||||
}
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
val expectedText = expectedClass.text
|
||||
val actualClass = if (expectedClass is KtObjectDeclaration) createObject(expectedText) else createClass(expectedText)
|
||||
val isInterface = expectedClass is KtClass && expectedClass.isInterface()
|
||||
actualClass.declarations.forEach {
|
||||
if (it.exists()) {
|
||||
it.delete()
|
||||
return@forEach
|
||||
}
|
||||
when (it) {
|
||||
is KtEnumEntry -> return@forEach
|
||||
is KtClassOrObject -> it.delete()
|
||||
@@ -229,12 +250,12 @@ private fun KtPsiFactory.generateClassOrObject(
|
||||
}
|
||||
}
|
||||
|
||||
declLoop@ for (expectedDeclaration in expectedClass.declarations) {
|
||||
declLoop@ for (expectedDeclaration in expectedClass.declarations.filter { !it.exists() }) {
|
||||
val descriptor = expectedDeclaration.toDescriptor() ?: continue
|
||||
val actualDeclaration: KtDeclaration = when (expectedDeclaration) {
|
||||
is KtClassOrObject ->
|
||||
if (expectedDeclaration !is KtEnumEntry) {
|
||||
generateClassOrObject(project, expectedDeclaration, actualNeeded = true)
|
||||
generateClassOrObjectByExpectedClass(project, expectedDeclaration, actualNeeded = true)
|
||||
}
|
||||
else {
|
||||
continue@declLoop
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// DISABLE-ERRORS
|
||||
|
||||
expect class My {
|
||||
fun foo(param: String): Int
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// DISABLE-ERRORS
|
||||
|
||||
expect class My {
|
||||
fun foo(param: String): Int
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Add missing actual members" "true"
|
||||
// DISABLE-ERRORS
|
||||
|
||||
actual class <caret>My
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Add missing actual members" "true"
|
||||
// DISABLE-ERRORS
|
||||
|
||||
actual class My {
|
||||
actual fun foo(param: String): Int {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// DISABLE-ERRORS
|
||||
|
||||
expect class My {
|
||||
fun foo(param: String): Int
|
||||
|
||||
fun foo(param: Int): Int
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// DISABLE-ERRORS
|
||||
|
||||
expect class My {
|
||||
fun foo(param: String): Int
|
||||
|
||||
fun foo(param: Int): Int
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add missing actual members" "true"
|
||||
// DISABLE-ERRORS
|
||||
|
||||
actual class <caret>My {
|
||||
fun foo(param: String) = 42
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add missing actual members" "true"
|
||||
// DISABLE-ERRORS
|
||||
|
||||
actual class My {
|
||||
fun foo(param: String) = 42
|
||||
actual fun foo(param: Int): Int {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// DISABLE-ERRORS
|
||||
|
||||
expect class My {
|
||||
fun foo(param: String): Int
|
||||
|
||||
val x: String
|
||||
|
||||
val pi: Double
|
||||
|
||||
val correct: Boolean
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// DISABLE-ERRORS
|
||||
|
||||
expect class My {
|
||||
fun foo(param: String): Int
|
||||
|
||||
val x: String
|
||||
|
||||
val pi: Double
|
||||
|
||||
val correct: Boolean
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Add missing actual members" "true"
|
||||
// DISABLE-ERRORS
|
||||
|
||||
actual class <caret>My {
|
||||
actual fun foo(param: String) = 42
|
||||
|
||||
actual val correct = true
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Add missing actual members" "true"
|
||||
// DISABLE-ERRORS
|
||||
|
||||
actual class <caret>My {
|
||||
actual fun foo(param: String) = 42
|
||||
|
||||
actual val correct = true
|
||||
actual val x: String
|
||||
get() = TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
actual val pi: Double
|
||||
get() = TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// DISABLE-ERRORS
|
||||
|
||||
expect class WithPrimaryConstructor(x: Int, s: String) {
|
||||
fun bar(x: String)
|
||||
|
||||
val z: Double
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// DISABLE-ERRORS
|
||||
|
||||
expect class WithPrimaryConstructor(x: Int, s: String) {
|
||||
fun bar(x: String)
|
||||
|
||||
val z: Double
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Add missing actual members" "true"
|
||||
// DISABLE-ERRORS
|
||||
|
||||
actual class <caret>WithPrimaryConstructor {
|
||||
fun bar(x: String) {}
|
||||
|
||||
val z: Double = 3.14
|
||||
}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// "Add missing actual members" "true"
|
||||
// DISABLE-ERRORS
|
||||
|
||||
actual class WithPrimaryConstructor actual constructor(x: Int, s: String) {
|
||||
fun bar(x: String) {}
|
||||
|
||||
val z: Double = 3.14
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// DISABLE-ERRORS
|
||||
|
||||
expect class WithSecondaryConstructor {
|
||||
constructor(x: Int, s: String)
|
||||
|
||||
fun bar(x: String)
|
||||
|
||||
val z: Double
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// DISABLE-ERRORS
|
||||
|
||||
expect class WithSecondaryConstructor {
|
||||
constructor(x: Int, s: String)
|
||||
|
||||
fun bar(x: String)
|
||||
|
||||
val z: Double
|
||||
}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// "Add missing actual members" "true"
|
||||
// DISABLE-ERRORS
|
||||
|
||||
actual class <caret>WithSecondaryConstructor {
|
||||
fun bar(x: String) {}
|
||||
|
||||
val z: Double = 3.14
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
// "Add missing actual members" "true"
|
||||
// DISABLE-ERRORS
|
||||
|
||||
actual class WithSecondaryConstructor {
|
||||
fun bar(x: String) {}
|
||||
|
||||
val z: Double = 3.14
|
||||
|
||||
actual constructor(x: Int, s: String) {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,21 @@ class QuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() {
|
||||
doMultiPlatformTest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClassFunction() {
|
||||
doMultiPlatformTest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClassOverloadedFunction() {
|
||||
doMultiPlatformTest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClassSomeProperties() {
|
||||
doMultiPlatformTest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeprecatedHeader() {
|
||||
doMultiPlatformTest()
|
||||
@@ -94,6 +109,11 @@ class QuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() {
|
||||
doMultiPlatformTest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNested() {
|
||||
doMultiPlatformTest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testObject() {
|
||||
doMultiPlatformTest()
|
||||
@@ -125,12 +145,12 @@ class QuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNested() {
|
||||
fun testPrimaryConstructor() {
|
||||
doMultiPlatformTest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPrimaryConstructor() {
|
||||
fun testPrimaryConstructorAbsence() {
|
||||
doMultiPlatformTest()
|
||||
}
|
||||
|
||||
@@ -144,6 +164,11 @@ class QuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() {
|
||||
doMultiPlatformTest(impls = "js" to TargetPlatformKind.JavaScript)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSecondaryConstructorAbsence() {
|
||||
doMultiPlatformTest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWithTest() {
|
||||
doMultiPlatformTest(expectName = "common", withTests = true)
|
||||
|
||||
Reference in New Issue
Block a user