FIR IDE: add quick fix to add initializer for MUST_BE_INITIALIZED_OR_BE_ABSTRACT
This commit is contained in:
committed by
Ilya Kirillov
parent
72f7405e4a
commit
d5ea68c585
@@ -1133,6 +1133,7 @@ fun main(args: Array<String>) {
|
||||
model("quickfix/override/typeMismatchOnOverride", pattern = pattern, filenameStartsLowerCase = true, recursive = false)
|
||||
model("quickfix/replaceWithSafeCall", pattern = pattern, filenameStartsLowerCase = true)
|
||||
model("quickfix/variables/changeMutability", pattern = pattern, filenameStartsLowerCase = true)
|
||||
model("quickfix/addInitializer", pattern = pattern, filenameStartsLowerCase = true)
|
||||
}
|
||||
|
||||
testClass<AbstractHLInspectionTest> {
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixesList
|
||||
import org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixesListBuilder
|
||||
import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic
|
||||
import org.jetbrains.kotlin.idea.quickfix.fixes.ChangeTypeQuickFix
|
||||
import org.jetbrains.kotlin.idea.quickfix.fixes.InitializePropertyQuickFixFactory
|
||||
import org.jetbrains.kotlin.idea.quickfix.fixes.ReplaceCallFixFactories
|
||||
|
||||
class MainKtQuickFixRegistrar : KtQuickFixRegistrar() {
|
||||
@@ -61,6 +62,7 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() {
|
||||
KtFirDiagnostic.MustBeInitializedOrBeAbstract::class,
|
||||
AddModifierFix.addAbstractModifier,
|
||||
)
|
||||
registerApplicator(InitializePropertyQuickFixFactory.initializePropertyFactory)
|
||||
}
|
||||
|
||||
private val overrides = KtQuickFixesListBuilder.registerPsiQuickFix {
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix.fixes
|
||||
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.api.applicator.HLApplicator
|
||||
import org.jetbrains.kotlin.idea.api.applicator.HLApplicatorInput
|
||||
import org.jetbrains.kotlin.idea.api.applicator.applicator
|
||||
import org.jetbrains.kotlin.idea.fir.api.fixes.HLQuickFix
|
||||
import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory
|
||||
import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic
|
||||
import org.jetbrains.kotlin.idea.frontend.api.types.defaultInitializer
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
|
||||
object InitializePropertyQuickFixFactory {
|
||||
|
||||
data class AddInitializerInput(val initializerText: String?) : HLApplicatorInput
|
||||
|
||||
private val addInitializerApplicator: HLApplicator<KtProperty, AddInitializerInput> = applicator {
|
||||
familyAndActionName(KotlinBundle.lazyMessage("add.initializer"))
|
||||
|
||||
applyToWithEditorRequired { property, input, project, editor ->
|
||||
val initializer = property.setInitializer(KtPsiFactory(project).createExpression(input.initializerText ?: "TODO()"))!!
|
||||
PsiDocumentManager.getInstance(project).commitDocument(editor.document)
|
||||
editor.selectionModel.setSelection(initializer.startOffset, initializer.endOffset)
|
||||
editor.caretModel.moveToOffset(initializer.endOffset)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
val initializePropertyFactory =
|
||||
diagnosticFixFactory<KtFirDiagnostic.MustBeInitializedOrBeAbstract> { diagnostic ->
|
||||
val property: KtProperty = diagnostic.psi
|
||||
buildList {
|
||||
add(
|
||||
HLQuickFix(
|
||||
property,
|
||||
AddInitializerInput(property.getReturnKtType().defaultInitializer),
|
||||
addInitializerApplicator
|
||||
)
|
||||
)
|
||||
|
||||
(property.containingClassOrObject as? KtClass)?.let { ktClass ->
|
||||
if (ktClass.isAnnotation() || ktClass.isInterface()) return@let
|
||||
|
||||
// TODO: Add quickfixes MoveToConstructorParameters and InitializeWithConstructorParameter after change signature
|
||||
// refactoring is available. See org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.idea.quickfix
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.codeInspection.InspectionProfileEntry
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.test.uitls.IgnoreTests
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
@@ -40,4 +39,4 @@ abstract class AbstractHighLevelQuickFixTest : AbstractQuickFixTest() {
|
||||
override fun parseInspectionsToEnable(beforeFileName: String, beforeFileText: String): List<InspectionProfileEntry> {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+98
@@ -1132,4 +1132,102 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/addInitializer")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AddInitializer extends AbstractHighLevelQuickFixTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInAddInitializer() throws Exception {
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/addInitializer"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@TestMetadata("localVar.kt")
|
||||
public void testLocalVar() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/localVar.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("memberExtensionProperty.kt")
|
||||
public void testMemberExtensionProperty() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/memberExtensionProperty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("memberExtensionPropertyVarGetterOnly.kt")
|
||||
public void testMemberExtensionPropertyVarGetterOnly() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/memberExtensionPropertyVarGetterOnly.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("memberExtensionPropertyVarSetterOnly.kt")
|
||||
public void testMemberExtensionPropertyVarSetterOnly() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/memberExtensionPropertyVarSetterOnly.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("memberProperty.kt")
|
||||
public void testMemberProperty() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/memberProperty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("memberPropertyVarGetterOnly.kt")
|
||||
public void testMemberPropertyVarGetterOnly() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/memberPropertyVarGetterOnly.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("memberPropertyVarSetterOnly.kt")
|
||||
public void testMemberPropertyVarSetterOnly() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/memberPropertyVarSetterOnly.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("memberPropertyWithAccessor.kt")
|
||||
public void testMemberPropertyWithAccessor() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/memberPropertyWithAccessor.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("memberPropertyWithDelegateRuntime.kt")
|
||||
public void testMemberPropertyWithDelegateRuntime() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/memberPropertyWithDelegateRuntime.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelExtensionProperty.kt")
|
||||
public void testTopLevelExtensionProperty() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/topLevelExtensionProperty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelExtensionPropertySetterOnly.kt")
|
||||
public void testTopLevelExtensionPropertySetterOnly() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/topLevelExtensionPropertySetterOnly.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelProperty.kt")
|
||||
public void testTopLevelProperty() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/topLevelProperty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelPropertyVarClass.kt")
|
||||
public void testTopLevelPropertyVarClass() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/topLevelPropertyVarClass.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelPropertyVarGetterOnly.kt")
|
||||
public void testTopLevelPropertyVarGetterOnly() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/topLevelPropertyVarGetterOnly.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelPropertyVarSetterOnly.kt")
|
||||
public void testTopLevelPropertyVarSetterOnly() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/topLevelPropertyVarSetterOnly.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelPropertyWithDelegateRuntime.kt")
|
||||
public void testTopLevelPropertyWithDelegateRuntime() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/topLevelPropertyWithDelegateRuntime.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelPropertyWithGetter.kt")
|
||||
public void testTopLevelPropertyWithGetter() throws Exception {
|
||||
runTest("idea/testData/quickfix/addInitializer/topLevelPropertyWithGetter.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-1
@@ -5,11 +5,22 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.frontend.api.types
|
||||
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.StandardNames
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
|
||||
val KtType.isMarkedNullable: Boolean get() = (this as? KtTypeWithNullability)?.nullability == KtTypeNullability.NULLABLE
|
||||
|
||||
val KtType.isUnit: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.UNIT)
|
||||
val KtType.isInt: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.INT)
|
||||
val KtType.isLong: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.LONG)
|
||||
val KtType.isShort: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.SHORT)
|
||||
val KtType.isByte: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.BYTE)
|
||||
val KtType.isFloat: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.FLOAT)
|
||||
val KtType.isDouble: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.DOUBLE)
|
||||
val KtType.isChar: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.CHAR)
|
||||
val KtType.isBoolean: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.BOOLEAN)
|
||||
val KtType.isString: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.STRING)
|
||||
|
||||
|
||||
fun KtType.isClassTypeWithClassId(classId: ClassId): Boolean {
|
||||
if (this !is KtClassType) return false
|
||||
@@ -18,4 +29,26 @@ fun KtType.isClassTypeWithClassId(classId: ClassId): Boolean {
|
||||
|
||||
private object DefaultTypeClassIds {
|
||||
val UNIT = ClassId.topLevel(StandardNames.FqNames.unit.toSafe())
|
||||
val INT = ClassId.topLevel(StandardNames.FqNames._int.toSafe())
|
||||
val LONG = ClassId.topLevel(StandardNames.FqNames._long.toSafe())
|
||||
val SHORT = ClassId.topLevel(StandardNames.FqNames._short.toSafe())
|
||||
val BYTE = ClassId.topLevel(StandardNames.FqNames._byte.toSafe())
|
||||
val FLOAT = ClassId.topLevel(StandardNames.FqNames._float.toSafe())
|
||||
val DOUBLE = ClassId.topLevel(StandardNames.FqNames._double.toSafe())
|
||||
val CHAR = ClassId.topLevel(StandardNames.FqNames._char.toSafe())
|
||||
val BOOLEAN = ClassId.topLevel(StandardNames.FqNames._boolean.toSafe())
|
||||
val STRING = ClassId.topLevel(StandardNames.FqNames.string.toSafe())
|
||||
}
|
||||
|
||||
val KtType.defaultInitializer: String?
|
||||
get() = when {
|
||||
isMarkedNullable -> "null"
|
||||
isInt || isLong || isShort || isByte -> "0"
|
||||
isFloat -> "0.0f"
|
||||
isDouble -> "0.0"
|
||||
isChar -> "'\\u0000'"
|
||||
isBoolean -> "false"
|
||||
isUnit -> "Unit"
|
||||
isString -> "\"\""
|
||||
else -> null
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$AddInitializerFix" "false"
|
||||
// "Add initializer" "false"
|
||||
fun test() {
|
||||
<caret>val n: Int
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$AddInitializerFix" "false"
|
||||
// "Add initializer" "false"
|
||||
// ACTION: Add getter
|
||||
// ACTION: Make internal
|
||||
// ACTION: Make private
|
||||
// ACTION: Make protected
|
||||
// ACTION: Make 'n' abstract
|
||||
// ACTION: Move to constructor
|
||||
// ERROR: Extension property must have accessors or be abstract
|
||||
class A {
|
||||
<caret>val Int.n: Int
|
||||
|
||||
+4
-1
@@ -1,7 +1,10 @@
|
||||
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$AddInitializerFix" "false"
|
||||
// "Add initializer" "false"
|
||||
// ACTION: Add setter
|
||||
// ACTION: Change to val
|
||||
// ACTION: Make internal
|
||||
// ACTION: Make private
|
||||
// ACTION: Make protected
|
||||
// ACTION: Remove explicit type specification
|
||||
// ERROR: Property must be initialized
|
||||
class A {
|
||||
<caret>var Int.n: Int
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$AddInitializerFix" "false"
|
||||
// "Add initializer" "false"
|
||||
// ACTION: Add getter
|
||||
// ACTION: Make internal
|
||||
// ACTION: Make private
|
||||
// ACTION: Make protected
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
class A {
|
||||
<caret>var n: Int
|
||||
get() = 1
|
||||
}
|
||||
}
|
||||
/* IGNORE_FIR */
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
class A {
|
||||
var n: Int = <selection>0</selection><caret>
|
||||
get() = 1
|
||||
}
|
||||
}
|
||||
/* IGNORE_FIR */
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
class A {
|
||||
<caret>var n: Int
|
||||
set(value: Int) {}
|
||||
}
|
||||
}
|
||||
/* IGNORE_FIR */
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
class A {
|
||||
var n: Int = <selection>0</selection><caret>
|
||||
set(value: Int) {}
|
||||
}
|
||||
}
|
||||
/* IGNORE_FIR */
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$AddInitializerFix" "false"
|
||||
// "Add initializer" "false"
|
||||
// ACTION: Make internal
|
||||
// ACTION: Make private
|
||||
// ACTION: Make protected
|
||||
// ACTION: Remove explicit type specification
|
||||
class A {
|
||||
<caret>val n: Int
|
||||
get() = 1
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$AddInitializerFix" "false"
|
||||
// "Add initializer" "false"
|
||||
// ACTION: Convert to ordinary property
|
||||
// ACTION: Make internal
|
||||
// ACTION: Make private
|
||||
// ACTION: Make protected
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$AddInitializerFix" "false"
|
||||
// "Add initializer" "false"
|
||||
// ACTION: Add getter
|
||||
// ACTION: Make internal
|
||||
// ACTION: Make private
|
||||
// ERROR: Extension property must have accessors or be abstract
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$AddInitializerFix" "false"
|
||||
// "Add initializer" "false"
|
||||
// ACTION: Add getter
|
||||
// ACTION: Make internal
|
||||
// ACTION: Make private
|
||||
// ERROR: Extension property must have accessors or be abstract
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
// "Add initializer" "true"
|
||||
<caret>val n: Int
|
||||
<caret>val n: Int
|
||||
/* IGNORE_FIR */
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
// "Add initializer" "true"
|
||||
val n: Int = <selection>0</selection><caret>
|
||||
val n: Int = <selection>0</selection><caret>
|
||||
/* IGNORE_FIR */
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// "Add initializer" "true"
|
||||
// WITH_RUNTIME
|
||||
class A
|
||||
<caret>var label: A
|
||||
<caret>var label: A
|
||||
/* IGNORE_FIR */
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// "Add initializer" "true"
|
||||
// WITH_RUNTIME
|
||||
class A
|
||||
var label: A = TODO()
|
||||
var label: A = TODO()
|
||||
/* IGNORE_FIR */
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// "Add initializer" "true"
|
||||
<caret>var n: Int
|
||||
get() = 1
|
||||
get() = 1
|
||||
/* IGNORE_FIR */
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
// "Add initializer" "true"
|
||||
var n: Int = <selection>0</selection><caret>
|
||||
get() = 1
|
||||
get() = 1
|
||||
/* IGNORE_FIR */
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// "Add initializer" "true"
|
||||
<caret>var n: Int
|
||||
set(value: Int) {}
|
||||
set(value: Int) {}
|
||||
/* IGNORE_FIR */
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
// "Add initializer" "true"
|
||||
var n: Int = <selection>0</selection><caret>
|
||||
set(value: Int) {}
|
||||
set(value: Int) {}
|
||||
/* IGNORE_FIR */
|
||||
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$AddInitializerFix" "false"
|
||||
// "Add initializer" "false"
|
||||
// WITH_RUNTIME
|
||||
// ACTION: Convert to ordinary property
|
||||
// ACTION: Create test
|
||||
// ACTION: Make internal
|
||||
// ACTION: Make private
|
||||
<caret>val n: Int by lazy { 0 }
|
||||
@@ -1,5 +1,7 @@
|
||||
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$AddInitializerFix" "false"
|
||||
// "Add initializer" "false"
|
||||
// ACTION: Create test
|
||||
// ACTION: Make internal
|
||||
// ACTION: Make private
|
||||
// ACTION: Remove explicit type specification
|
||||
<caret>val n: Int
|
||||
get() = 1
|
||||
Reference in New Issue
Block a user