From e92af08c9835d039490091a4d2fe832f9dd1cecc Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Mon, 2 Oct 2017 12:36:22 +0300 Subject: [PATCH] Introduce "Assign to property" quick-fix #KT-17204 Fixed --- .../idea/quickfix/AssignToPropertyFix.kt | 82 +++++++++++++++++++ .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 4 +- .../assignToProperty/differentNameProperty.kt | 9 ++ .../differentNameProperty2.kt | 7 ++ .../assignToProperty/differentTypeProperty.kt | 9 ++ .../differentTypeProperty2.kt | 7 ++ .../inSecondaryConstructor.kt | 8 ++ .../inSecondaryConstructor.kt.after | 8 ++ .../quickfix/assignToProperty/noProperty.kt | 7 ++ .../assignToProperty/qualifiedThis.kt | 11 +++ .../assignToProperty/qualifiedThis.kt.after | 11 +++ .../quickfix/assignToProperty/sameProperty.kt | 8 ++ .../assignToProperty/sameProperty.kt.after | 8 ++ .../assignToProperty/sameProperty2.kt | 8 ++ .../assignToProperty/sameProperty2.kt.after | 8 ++ .../assignToProperty/sameProperty3.kt | 6 ++ .../assignToProperty/sameProperty3.kt.after | 6 ++ .../quickfix/assignToProperty/valProperty.kt | 9 ++ .../quickfix/assignToProperty/valProperty2.kt | 7 ++ .../idea/quickfix/QuickFixTestGenerated.java | 81 ++++++++++++++++++ 20 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/AssignToPropertyFix.kt create mode 100644 idea/testData/quickfix/assignToProperty/differentNameProperty.kt create mode 100644 idea/testData/quickfix/assignToProperty/differentNameProperty2.kt create mode 100644 idea/testData/quickfix/assignToProperty/differentTypeProperty.kt create mode 100644 idea/testData/quickfix/assignToProperty/differentTypeProperty2.kt create mode 100644 idea/testData/quickfix/assignToProperty/inSecondaryConstructor.kt create mode 100644 idea/testData/quickfix/assignToProperty/inSecondaryConstructor.kt.after create mode 100644 idea/testData/quickfix/assignToProperty/noProperty.kt create mode 100644 idea/testData/quickfix/assignToProperty/qualifiedThis.kt create mode 100644 idea/testData/quickfix/assignToProperty/qualifiedThis.kt.after create mode 100644 idea/testData/quickfix/assignToProperty/sameProperty.kt create mode 100644 idea/testData/quickfix/assignToProperty/sameProperty.kt.after create mode 100644 idea/testData/quickfix/assignToProperty/sameProperty2.kt create mode 100644 idea/testData/quickfix/assignToProperty/sameProperty2.kt.after create mode 100644 idea/testData/quickfix/assignToProperty/sameProperty3.kt create mode 100644 idea/testData/quickfix/assignToProperty/sameProperty3.kt.after create mode 100644 idea/testData/quickfix/assignToProperty/valProperty.kt create mode 100644 idea/testData/quickfix/assignToProperty/valProperty2.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AssignToPropertyFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AssignToPropertyFix.kt new file mode 100644 index 00000000000..779c4c14f90 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AssignToPropertyFix.kt @@ -0,0 +1,82 @@ +/* + * 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 com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.containingClass +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy +import org.jetbrains.kotlin.types.KotlinType + +class AssignToPropertyFix(element: KtNameReferenceExpression) : KotlinQuickFixAction(element) { + + override fun getText() = "Assign to property" + + override fun getFamilyName() = text + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val element = element ?: return + val psiFactory = KtPsiFactory(element) + if (element.getResolutionScope().getImplicitReceiversHierarchy().size == 1) { + element.replace(psiFactory.createExpressionByPattern("this.$0", element)) + } + else { + element.containingClass()?.name?.let { + element.replace(psiFactory.createExpressionByPattern("this@$0.$1", it, element)) + } + } + } + + companion object : KotlinSingleIntentionActionFactory() { + private fun KtCallableDeclaration.hasNameAndTypeOf(name: Name, type: KotlinType) = + nameAsName == name && (resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType == type + + override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction? { + val expression = diagnostic.psiElement as? KtNameReferenceExpression ?: return null + + val containingClass = expression.containingClass() ?: return null + val right = (expression.parent as? KtBinaryExpression)?.right ?: return null + val type = expression.analyze().getType(right) ?: return null + val name = expression.getReferencedNameAsName() + + val inSecondaryConstructor = expression.getStrictParentOfType() != null + val hasAssignableProperty = containingClass.getProperties().any { + (inSecondaryConstructor || it.isVar) && + it.hasNameAndTypeOf(name, type) + } + val hasAssignablePropertyInPrimaryConstructor = containingClass.primaryConstructor?.valueParameters?.any { + it.valOrVarKeyword?.node?.elementType == KtTokens.VAR_KEYWORD && + it.hasNameAndTypeOf(name, type) + } ?: false + + if (!hasAssignableProperty && !hasAssignablePropertyInPrimaryConstructor) return null + + return AssignToPropertyFix(expression) + } + } + +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index b212203a1bf..a274d837434 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -193,8 +193,8 @@ class QuickFixRegistrar : QuickFixContributor { MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED.registerActions(implementMembersHandler, implementMembersAsParametersHandler) VAL_WITH_SETTER.registerFactory(ChangeVariableMutabilityFix.VAL_WITH_SETTER_FACTORY) - VAL_REASSIGNMENT.registerFactory(ChangeVariableMutabilityFix.VAL_REASSIGNMENT_FACTORY) - VAL_REASSIGNMENT.registerFactory(LiftAssignmentOutOfTryFix) + VAL_REASSIGNMENT.registerFactory( + ChangeVariableMutabilityFix.VAL_REASSIGNMENT_FACTORY, LiftAssignmentOutOfTryFix, AssignToPropertyFix) CAPTURED_VAL_INITIALIZATION.registerFactory(ChangeVariableMutabilityFix.CAPTURED_VAL_INITIALIZATION_FACTORY) CAPTURED_MEMBER_VAL_INITIALIZATION.registerFactory(ChangeVariableMutabilityFix.CAPTURED_MEMBER_VAL_INITIALIZATION_FACTORY) VAR_OVERRIDDEN_BY_VAL.registerFactory(ChangeVariableMutabilityFix.VAR_OVERRIDDEN_BY_VAL_FACTORY) diff --git a/idea/testData/quickfix/assignToProperty/differentNameProperty.kt b/idea/testData/quickfix/assignToProperty/differentNameProperty.kt new file mode 100644 index 00000000000..60c793c0eae --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/differentNameProperty.kt @@ -0,0 +1,9 @@ +// "Assign to property" "false" +// ERROR: Val cannot be reassigned +class Test { + var bar = 1 + + fun test(foo: Int) { + foo = foo + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/differentNameProperty2.kt b/idea/testData/quickfix/assignToProperty/differentNameProperty2.kt new file mode 100644 index 00000000000..9a8400d8e5e --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/differentNameProperty2.kt @@ -0,0 +1,7 @@ +// "Assign to property" "false" +// ERROR: Val cannot be reassigned +class Test(var bar: Int) { + fun test(foo: Int) { + foo = foo + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/differentTypeProperty.kt b/idea/testData/quickfix/assignToProperty/differentTypeProperty.kt new file mode 100644 index 00000000000..cd7a4782d81 --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/differentTypeProperty.kt @@ -0,0 +1,9 @@ +// "Assign to property" "false" +// ERROR: Val cannot be reassigned +class Test { + var foo = "1" + + fun test(foo: Int) { + foo = foo + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/differentTypeProperty2.kt b/idea/testData/quickfix/assignToProperty/differentTypeProperty2.kt new file mode 100644 index 00000000000..696791dcd0f --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/differentTypeProperty2.kt @@ -0,0 +1,7 @@ +// "Assign to property" "false" +// ERROR: Val cannot be reassigned +class Test(var foo: String) { + fun test(foo: Int) { + foo = foo + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/inSecondaryConstructor.kt b/idea/testData/quickfix/assignToProperty/inSecondaryConstructor.kt new file mode 100644 index 00000000000..0c0af3761b0 --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/inSecondaryConstructor.kt @@ -0,0 +1,8 @@ +// "Assign to property" "true" +class Test { + val foo: Int + + constructor(foo: Int) { + foo = foo + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/inSecondaryConstructor.kt.after b/idea/testData/quickfix/assignToProperty/inSecondaryConstructor.kt.after new file mode 100644 index 00000000000..af601fdaf9d --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/inSecondaryConstructor.kt.after @@ -0,0 +1,8 @@ +// "Assign to property" "true" +class Test { + val foo: Int + + constructor(foo: Int) { + this.foo = foo + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/noProperty.kt b/idea/testData/quickfix/assignToProperty/noProperty.kt new file mode 100644 index 00000000000..de3079128ed --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/noProperty.kt @@ -0,0 +1,7 @@ +// "Assign to property" "false" +// ERROR: Val cannot be reassigned +class Test(foo: Int) { + fun test(foo: Int) { + foo = foo + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/qualifiedThis.kt b/idea/testData/quickfix/assignToProperty/qualifiedThis.kt new file mode 100644 index 00000000000..a1ae442f569 --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/qualifiedThis.kt @@ -0,0 +1,11 @@ +// "Assign to property" "true" +// WITH_RUNTIME +class Test { + var foo = 1 + + fun test(foo: Int) { + "".run { + foo = foo + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/qualifiedThis.kt.after b/idea/testData/quickfix/assignToProperty/qualifiedThis.kt.after new file mode 100644 index 00000000000..0374aeab6ef --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/qualifiedThis.kt.after @@ -0,0 +1,11 @@ +// "Assign to property" "true" +// WITH_RUNTIME +class Test { + var foo = 1 + + fun test(foo: Int) { + "".run { + this@Test.foo = foo + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/sameProperty.kt b/idea/testData/quickfix/assignToProperty/sameProperty.kt new file mode 100644 index 00000000000..6f16568639e --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/sameProperty.kt @@ -0,0 +1,8 @@ +// "Assign to property" "true" +class Test { + var foo = 1 + + fun test(foo: Int) { + foo = foo + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/sameProperty.kt.after b/idea/testData/quickfix/assignToProperty/sameProperty.kt.after new file mode 100644 index 00000000000..98b8e80d904 --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/sameProperty.kt.after @@ -0,0 +1,8 @@ +// "Assign to property" "true" +class Test { + var foo = 1 + + fun test(foo: Int) { + this.foo = foo + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/sameProperty2.kt b/idea/testData/quickfix/assignToProperty/sameProperty2.kt new file mode 100644 index 00000000000..d5a80702527 --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/sameProperty2.kt @@ -0,0 +1,8 @@ +// "Assign to property" "true" +class Test { + var foo = 1 + + fun test(foo: String) { + foo = 2 + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/sameProperty2.kt.after b/idea/testData/quickfix/assignToProperty/sameProperty2.kt.after new file mode 100644 index 00000000000..0fb31116a24 --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/sameProperty2.kt.after @@ -0,0 +1,8 @@ +// "Assign to property" "true" +class Test { + var foo = 1 + + fun test(foo: String) { + this.foo = 2 + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/sameProperty3.kt b/idea/testData/quickfix/assignToProperty/sameProperty3.kt new file mode 100644 index 00000000000..d2fe5f6e8d9 --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/sameProperty3.kt @@ -0,0 +1,6 @@ +// "Assign to property" "true" +class Test(var foo: Int) { + fun test(foo: Int) { + foo = foo + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/sameProperty3.kt.after b/idea/testData/quickfix/assignToProperty/sameProperty3.kt.after new file mode 100644 index 00000000000..0821c2a30eb --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/sameProperty3.kt.after @@ -0,0 +1,6 @@ +// "Assign to property" "true" +class Test(var foo: Int) { + fun test(foo: Int) { + this.foo = foo + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/valProperty.kt b/idea/testData/quickfix/assignToProperty/valProperty.kt new file mode 100644 index 00000000000..8e54bf9b5ab --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/valProperty.kt @@ -0,0 +1,9 @@ +// "Assign to property" "false" +// ERROR: Val cannot be reassigned +class Test { + val foo = 1 + + fun test(foo: Int) { + foo = foo + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/assignToProperty/valProperty2.kt b/idea/testData/quickfix/assignToProperty/valProperty2.kt new file mode 100644 index 00000000000..ca65b1989f0 --- /dev/null +++ b/idea/testData/quickfix/assignToProperty/valProperty2.kt @@ -0,0 +1,7 @@ +// "Assign to property" "false" +// ERROR: Val cannot be reassigned +class Test(val foo: Int) { + fun test(foo: Int) { + foo = foo + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 9c7ef95138d..4b6c8199d8a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -954,6 +954,87 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/assignToProperty") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class AssignToProperty extends AbstractQuickFixTest { + public void testAllFilesPresentInAssignToProperty() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/assignToProperty"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("differentNameProperty.kt") + public void testDifferentNameProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/assignToProperty/differentNameProperty.kt"); + doTest(fileName); + } + + @TestMetadata("differentNameProperty2.kt") + public void testDifferentNameProperty2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/assignToProperty/differentNameProperty2.kt"); + doTest(fileName); + } + + @TestMetadata("differentTypeProperty.kt") + public void testDifferentTypeProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/assignToProperty/differentTypeProperty.kt"); + doTest(fileName); + } + + @TestMetadata("differentTypeProperty2.kt") + public void testDifferentTypeProperty2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/assignToProperty/differentTypeProperty2.kt"); + doTest(fileName); + } + + @TestMetadata("inSecondaryConstructor.kt") + public void testInSecondaryConstructor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/assignToProperty/inSecondaryConstructor.kt"); + doTest(fileName); + } + + @TestMetadata("noProperty.kt") + public void testNoProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/assignToProperty/noProperty.kt"); + doTest(fileName); + } + + @TestMetadata("qualifiedThis.kt") + public void testQualifiedThis() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/assignToProperty/qualifiedThis.kt"); + doTest(fileName); + } + + @TestMetadata("sameProperty.kt") + public void testSameProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/assignToProperty/sameProperty.kt"); + doTest(fileName); + } + + @TestMetadata("sameProperty2.kt") + public void testSameProperty2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/assignToProperty/sameProperty2.kt"); + doTest(fileName); + } + + @TestMetadata("sameProperty3.kt") + public void testSameProperty3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/assignToProperty/sameProperty3.kt"); + doTest(fileName); + } + + @TestMetadata("valProperty.kt") + public void testValProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/assignToProperty/valProperty.kt"); + doTest(fileName); + } + + @TestMetadata("valProperty2.kt") + public void testValProperty2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/assignToProperty/valProperty2.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/autoImports") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)