FIR IDE: add quick fix to add initializer for MUST_BE_INITIALIZED_OR_BE_ABSTRACT

This commit is contained in:
Tianyu Geng
2021-03-22 17:42:11 -07:00
committed by Ilya Kirillov
parent 72f7405e4a
commit d5ea68c585
28 changed files with 245 additions and 26 deletions
@@ -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 {
@@ -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
}
}
}
}
@@ -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()
}
}
}
@@ -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");
}
}
}
@@ -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
View File
@@ -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
@@ -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
@@ -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
@@ -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
View File
@@ -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 */
@@ -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 */
@@ -1,3 +1,4 @@
// "Add initializer" "true"
var n: Int = <selection>0</selection><caret>
set(value: Int) {}
set(value: Int) {}
/* IGNORE_FIR */
@@ -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