FIR IDE: AddToString quickfix

This commit is contained in:
Tianyu Geng
2021-06-11 14:11:07 -07:00
committed by Ilya Kirillov
parent 1b8b5b9a79
commit f6078b24df
15 changed files with 158 additions and 8 deletions
@@ -1183,6 +1183,7 @@ fun main(args: Array<String>) {
model("quickfix/wrapWithSafeLetCall", pattern = pattern, filenameStartsLowerCase = true)
model("quickfix/typeMismatch/componentFunctionReturnTypeMismatch", pattern = pattern, filenameStartsLowerCase = true)
model("quickfix/typeMismatch/typeMismatchOnReturnedExpression", pattern = pattern, filenameStartsLowerCase = true)
model("quickfix/toString", pattern = pattern, filenameStartsLowerCase = true)
}
testClass<AbstractHighLevelQuickFixMultiFileTest> {
@@ -137,11 +137,18 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() {
registerPsiQuickFixes(KtFirDiagnostic.InapplicableLateinitModifier::class, RemoveNullableFix.removeForLateInitProperty)
}
private val returnTypes = KtQuickFixesListBuilder.registerPsiQuickFix {
private val typeMismatch = KtQuickFixesListBuilder.registerPsiQuickFix {
registerApplicator(ChangeTypeQuickFixFactories.componentFunctionReturnTypeMismatch)
registerApplicator(ChangeTypeQuickFixFactories.returnTypeMismatch)
registerApplicator(AddToStringFixFactories.typeMismatch)
registerApplicator(AddToStringFixFactories.argumentTypeMismatch)
registerApplicator(AddToStringFixFactories.assignmentTypeMismatch)
registerApplicator(AddToStringFixFactories.returnTypeMismatch)
registerApplicator(AddToStringFixFactories.initializerTypeMismatch)
}
override val list: KtQuickFixesList = KtQuickFixesList.createCombined(
keywords,
propertyInitialization,
@@ -149,6 +156,6 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() {
imports,
mutability,
expressions,
returnTypes
typeMismatch
)
}
@@ -0,0 +1,49 @@
/*
* 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.PsiElement
import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.idea.quickfix.AddToStringFix
import org.jetbrains.kotlin.psi.KtExpression
object AddToStringFixFactories {
@OptIn(ExperimentalStdlibApi::class)
private fun KtAnalysisSession.getFixes(element: PsiElement?, expectedType: KtType, actualType: KtType): List<AddToStringFix> {
if (element !is KtExpression) return emptyList()
return buildList {
if (expectedType.isString || expectedType.isCharSequence) {
add(AddToStringFix(element, false))
if (expectedType.isMarkedNullable && actualType.isMarkedNullable) {
add(AddToStringFix(element, true))
}
}
}
}
val typeMismatch = diagnosticFixFactory(KtFirDiagnostic.TypeMismatch::class) { diagnostic ->
getFixes(diagnostic.psi, diagnostic.expectedType, diagnostic.actualType)
}
val argumentTypeMismatch = diagnosticFixFactory(KtFirDiagnostic.ArgumentTypeMismatch::class) { diagnostic ->
getFixes(diagnostic.psi, diagnostic.expectedType, diagnostic.actualType)
}
val assignmentTypeMismatch = diagnosticFixFactory(KtFirDiagnostic.AssignmentTypeMismatch::class) { diagnostic ->
getFixes(diagnostic.psi, diagnostic.expectedType, diagnostic.actualType)
}
val returnTypeMismatch = diagnosticFixFactory(KtFirDiagnostic.ReturnTypeMismatch::class) { diagnostic ->
getFixes(diagnostic.psi, diagnostic.expectedType, diagnostic.actualType)
}
val initializerTypeMismatch = diagnosticFixFactory(KtFirDiagnostic.InitializerTypeMismatch::class) { diagnostic ->
getFixes(diagnostic.psi.initializer, diagnostic.expectedType, diagnostic.actualType)
}
}
@@ -2334,4 +2334,52 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes
runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/typeMismatchInReturnStatement.kt");
}
}
@TestMetadata("idea/testData/quickfix/toString")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ToString extends AbstractHighLevelQuickFixTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInToString() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/toString"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true);
}
@TestMetadata("callArgument.kt")
public void testCallArgument() throws Exception {
runTest("idea/testData/quickfix/toString/callArgument.kt");
}
@TestMetadata("notNullableExpectedNullable.kt")
public void testNotNullableExpectedNullable() throws Exception {
runTest("idea/testData/quickfix/toString/notNullableExpectedNullable.kt");
}
@TestMetadata("nullableExpectedNotNullable.kt")
public void testNullableExpectedNotNullable() throws Exception {
runTest("idea/testData/quickfix/toString/nullableExpectedNotNullable.kt");
}
@TestMetadata("nullableExpectedNullable.kt")
public void testNullableExpectedNullable() throws Exception {
runTest("idea/testData/quickfix/toString/nullableExpectedNullable.kt");
}
@TestMetadata("propertyInitializer.kt")
public void testPropertyInitializer() throws Exception {
runTest("idea/testData/quickfix/toString/propertyInitializer.kt");
}
@TestMetadata("returnStatement.kt")
public void testReturnStatement() throws Exception {
runTest("idea/testData/quickfix/toString/returnStatement.kt");
}
@TestMetadata("variableAssignment.kt")
public void testVariableAssignment() throws Exception {
runTest("idea/testData/quickfix/toString/variableAssignment.kt");
}
}
}
@@ -38,6 +38,7 @@ interface KtTypeInfoProviderMixIn : KtAnalysisSessionMixIn {
val KtType.isChar: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.CHAR)
val KtType.isBoolean: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.BOOLEAN)
val KtType.isString: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.STRING)
val KtType.isCharSequence: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.CHAR_SEQUENCE)
val KtType.isAny: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.ANY)
val KtType.isUInt: Boolean get() = isClassTypeWithClassId(StandardNames.FqNames.uInt)
@@ -85,6 +86,7 @@ object DefaultTypeClassIds {
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 CHAR_SEQUENCE = ClassId.topLevel(StandardNames.FqNames.charSequence.toSafe())
val ANY = ClassId.topLevel(StandardNames.FqNames.any.toSafe())
val PRIMITIVES = setOf(INT, LONG, SHORT, BYTE, FLOAT, DOUBLE, CHAR, BOOLEAN)
}
@@ -17,12 +17,12 @@ import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.createExpressionByPattern
import org.jetbrains.kotlin.psi.psiUtil.endOffset
class AddToStringFix(element: KtExpression, private val nullable: Boolean) :
class AddToStringFix(element: KtExpression, private val useSafeCallOperator: Boolean) :
KotlinPsiOnlyQuickFixAction<KtExpression>(element), KotlinUniversalQuickFix, LowPriorityAction {
override fun getFamilyName() = KotlinBundle.message("fix.add.tostring.call.family")
override fun getText(): String {
return when (nullable) {
return when (useSafeCallOperator) {
true -> KotlinBundle.message("fix.add.tostring.call.text.safe")
false -> KotlinBundle.message("fix.add.tostring.call.text")
}
@@ -30,7 +30,7 @@ class AddToStringFix(element: KtExpression, private val nullable: Boolean) :
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val element = element ?: return
val pattern = if (nullable) "$0?.toString()" else "$0.toString()"
val pattern = if (useSafeCallOperator) "$0?.toString()" else "$0.toString()"
val expressionToInsert = KtPsiFactory(file).createExpressionByPattern(pattern, element)
val newExpression = element.replaced(expressionToInsert)
editor?.caretModel?.moveToOffset(newExpression.endOffset)
@@ -0,0 +1,3 @@
// "Add 'toString()' call" "true"
val s: String = 1 <caret>+ 2 * 3
@@ -0,0 +1,3 @@
// "Add 'toString()' call" "true"
val s: String = (1 + 2 * 3).toString()<caret>
+5
View File
@@ -0,0 +1,5 @@
// "Add 'toString()' call" "true"
fun test(): String {
return 1 <caret>+ 2 * 3
}
@@ -0,0 +1,5 @@
// "Add 'toString()' call" "true"
fun test(): String {
return (1 + 2 * 3).toString()<caret>
}
+6
View File
@@ -0,0 +1,6 @@
// "Add 'toString()' call" "true"
fun test() {
var s: String = ""
s = 1 <caret>+ 2 * 3
}
@@ -0,0 +1,6 @@
// "Add 'toString()' call" "true"
fun test() {
var s: String = ""
s = (1 + 2 * 3).toString()<caret>
}
@@ -13668,6 +13668,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/toString"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true);
}
@TestMetadata("callArgument.kt")
public void testCallArgument() throws Exception {
runTest("idea/testData/quickfix/toString/callArgument.kt");
}
@TestMetadata("notNullableExpectedNullable.kt")
public void testNotNullableExpectedNullable() throws Exception {
runTest("idea/testData/quickfix/toString/notNullableExpectedNullable.kt");
@@ -13683,9 +13688,19 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/toString/nullableExpectedNullable.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("idea/testData/quickfix/toString/simple.kt");
@TestMetadata("propertyInitializer.kt")
public void testPropertyInitializer() throws Exception {
runTest("idea/testData/quickfix/toString/propertyInitializer.kt");
}
@TestMetadata("returnStatement.kt")
public void testReturnStatement() throws Exception {
runTest("idea/testData/quickfix/toString/returnStatement.kt");
}
@TestMetadata("variableAssignment.kt")
public void testVariableAssignment() throws Exception {
runTest("idea/testData/quickfix/toString/variableAssignment.kt");
}
}