From cee45f6a0a1a3a9fdbde0e2b36686231dd62d636 Mon Sep 17 00:00:00 2001 From: Wojciech Lopata Date: Tue, 26 Feb 2013 17:15:26 +0100 Subject: [PATCH] QuickFix for OVERRIDING_FINAL_MEMBER --- .../jetbrains/jet/plugin/JetBundle.properties | 1 + .../quickfix/MakeOverriddenMemberOpenFix.java | 122 ++++++++++++++++++ .../jet/plugin/quickfix/QuickFixes.java | 2 + .../afterOverriddingMultipleFinalMethods.kt | 20 +++ .../override/afterOverridingFinalMethod.kt | 8 ++ .../override/afterOverridingFinalProperty.kt | 8 ++ .../beforeOverriddingMultipleFinalMethods.kt | 20 +++ .../beforeOverridingFinalJavaMethod.kt | 5 + .../override/beforeOverridingFinalMethod.kt | 8 ++ .../override/beforeOverridingFinalProperty.kt | 8 ++ ...idingWritableAndNotWritableFinalMembers.kt | 12 ++ .../quickfix/QuickFixTestGenerated.java | 25 ++++ 12 files changed, 239 insertions(+) create mode 100644 idea/src/org/jetbrains/jet/plugin/quickfix/MakeOverriddenMemberOpenFix.java create mode 100644 idea/testData/quickfix/override/afterOverriddingMultipleFinalMethods.kt create mode 100644 idea/testData/quickfix/override/afterOverridingFinalMethod.kt create mode 100644 idea/testData/quickfix/override/afterOverridingFinalProperty.kt create mode 100644 idea/testData/quickfix/override/beforeOverriddingMultipleFinalMethods.kt create mode 100644 idea/testData/quickfix/override/beforeOverridingFinalJavaMethod.kt create mode 100644 idea/testData/quickfix/override/beforeOverridingFinalMethod.kt create mode 100644 idea/testData/quickfix/override/beforeOverridingFinalProperty.kt create mode 100644 idea/testData/quickfix/override/beforeOverridingWritableAndNotWritableFinalMembers.kt diff --git a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties index cb6323ee657..837c330fd92 100644 --- a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties @@ -12,6 +12,7 @@ remove.function.body=Remove function body make.element.modifier=Make {0} {1} add.modifier=Add ''{0}'' modifier add.modifier.family=Add Modifier +make.element.in.classifiers.open=Make ''{0}'' in {1} open make.class.annotation.class=Make ''{0}'' an annotation class make.class.annotation.class.family=Make Class an Annotation Class add.star.projections=Add ''{0}'' diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/MakeOverriddenMemberOpenFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/MakeOverriddenMemberOpenFix.java new file mode 100644 index 00000000000..edd88b97e01 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/MakeOverriddenMemberOpenFix.java @@ -0,0 +1,122 @@ +/* + * Copyright 2010-2013 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.jet.plugin.quickfix; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.descriptors.CallableMemberDescriptor; +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.diagnostics.Diagnostic; +import org.jetbrains.jet.lang.psi.JetDeclaration; +import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.resolve.BindingContextUtils; +import org.jetbrains.jet.lang.resolve.lazy.ResolveSession; +import org.jetbrains.jet.plugin.JetBundle; +import org.jetbrains.jet.plugin.project.WholeProjectAnalyzerFacade; + +import java.util.*; + +import static org.jetbrains.jet.lexer.JetTokens.OPEN_KEYWORD; + +public class MakeOverriddenMemberOpenFix extends JetIntentionAction { + private final List overriddenMembers = new ArrayList(); + private final List containingDeclarationsNames = new ArrayList(); + + public MakeOverriddenMemberOpenFix(@NotNull JetDeclaration declaration) { + super(declaration); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + if (!super.isAvailable(project, editor, file) || !(file instanceof JetFile)) { + return false; + } + + // When running single test 'isAvailable()' is invoked multiple times, so we need to clear lists. + overriddenMembers.clear(); + containingDeclarationsNames.clear(); + + ResolveSession resolveSession = WholeProjectAnalyzerFacade.getLazyResolveSessionForFile((JetFile) file); + DeclarationDescriptor descriptor = resolveSession.resolveToDescriptor(element); + if (!(descriptor instanceof CallableMemberDescriptor)) return false; + CallableMemberDescriptor callableMemberDescriptor = (CallableMemberDescriptor) descriptor; + for (CallableMemberDescriptor overriddenDescriptor : callableMemberDescriptor.getOverriddenDescriptors()) { + if (!overriddenDescriptor.getModality().isOverridable()) { + PsiElement overriddenMember = + BindingContextUtils.descriptorToDeclaration(resolveSession.getBindingContext(), overriddenDescriptor); + if (overriddenMember == null || !overriddenMember.isWritable()) { + return false; + } + String containingDeclarationName = overriddenDescriptor.getContainingDeclaration().getName().getName(); + overriddenMembers.add(overriddenMember); + containingDeclarationsNames.add(containingDeclarationName); + } + } + return overriddenMembers.size() > 0; + } + + @NotNull + @Override + public String getText() { + if (overriddenMembers.size() == 1) { + return JetBundle.message("make.element.modifier", containingDeclarationsNames.get(0) + "." + element.getName(), OPEN_KEYWORD); + } + + StringBuilder declarations = new StringBuilder(); + Collections.sort(containingDeclarationsNames); + for (int i = 0; i < containingDeclarationsNames.size() - 2; i++) { + declarations.append(containingDeclarationsNames.get(i)); + declarations.append(", "); + } + declarations.append(containingDeclarationsNames.get(containingDeclarationsNames.size() - 2)); + declarations.append(" and "); + declarations.append(containingDeclarationsNames.get(containingDeclarationsNames.size() - 1)); + return JetBundle.message("make.element.in.classifiers.open", element.getName(), declarations.toString()); + } + + @NotNull + @Override + public String getFamilyName() { + return JetBundle.message("add.modifier.family"); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + for (PsiElement overriddenMember : overriddenMembers) { + overriddenMember.replace(AddModifierFix.addModifierWithDefaultReplacement(overriddenMember, OPEN_KEYWORD, project, false)); + } + } + + @NotNull + public static JetIntentionActionFactory createFactory() { + return new JetIntentionActionFactory() { + @Nullable + @Override + public IntentionAction createAction(Diagnostic diagnostic) { + JetDeclaration declaration = QuickFixUtil.getParentElementOfType(diagnostic, JetDeclaration.class); + assert declaration != null; + return new MakeOverriddenMemberOpenFix(declaration); + } + }; + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java index 09f78be64f2..41f735028b9 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java @@ -194,6 +194,8 @@ public class QuickFixes { factories.put(FINAL_SUPERTYPE, addOpenModifierToClassDeclarationFix); factories.put(FINAL_UPPER_BOUND, addOpenModifierToClassDeclarationFix); + factories.put(OVERRIDING_FINAL_MEMBER, MakeOverriddenMemberOpenFix.createFactory()); + factories.put(PARAMETER_NAME_CHANGED_ON_OVERRIDE, RenameParameterToMatchOverriddenMethodFix.createFactory()); factories.put(OPEN_MODIFIER_IN_ENUM, RemoveModifierFix.createRemoveModifierFromListOwnerFactory(OPEN_KEYWORD)); diff --git a/idea/testData/quickfix/override/afterOverriddingMultipleFinalMethods.kt b/idea/testData/quickfix/override/afterOverriddingMultipleFinalMethods.kt new file mode 100644 index 00000000000..40e7bf22aa4 --- /dev/null +++ b/idea/testData/quickfix/override/afterOverriddingMultipleFinalMethods.kt @@ -0,0 +1,20 @@ +// "Make 'foo' in A, X and Y open" "true" +open class A { + open fun foo() {} +} + +trait X { + open fun foo() {} +} + +trait Y { + open fun foo() {} +} + +trait Z { + fun foo() {} +} + +class B : A(), X, Y, Z { + override fun foo() {} +} diff --git a/idea/testData/quickfix/override/afterOverridingFinalMethod.kt b/idea/testData/quickfix/override/afterOverridingFinalMethod.kt new file mode 100644 index 00000000000..9d2b9f3dfd9 --- /dev/null +++ b/idea/testData/quickfix/override/afterOverridingFinalMethod.kt @@ -0,0 +1,8 @@ +// "Make A.foo open" "true" +open class A { + open fun foo() {} +} + +class B : A() { + override fun foo() {} +} \ No newline at end of file diff --git a/idea/testData/quickfix/override/afterOverridingFinalProperty.kt b/idea/testData/quickfix/override/afterOverridingFinalProperty.kt new file mode 100644 index 00000000000..8753fbd0b55 --- /dev/null +++ b/idea/testData/quickfix/override/afterOverridingFinalProperty.kt @@ -0,0 +1,8 @@ +// "Make A.x open" "true" +open class A { + open var x = 42; +} + +class B : A() { + override var x = 24; +} \ No newline at end of file diff --git a/idea/testData/quickfix/override/beforeOverriddingMultipleFinalMethods.kt b/idea/testData/quickfix/override/beforeOverriddingMultipleFinalMethods.kt new file mode 100644 index 00000000000..1348a5ef6ba --- /dev/null +++ b/idea/testData/quickfix/override/beforeOverriddingMultipleFinalMethods.kt @@ -0,0 +1,20 @@ +// "Make 'foo' in A, X and Y open" "true" +open class A { + fun foo() {} +} + +trait X { + final fun foo() {} +} + +trait Y { + final fun foo() {} +} + +trait Z { + fun foo() {} +} + +class B : A(), X, Y, Z { + override fun foo() {} +} diff --git a/idea/testData/quickfix/override/beforeOverridingFinalJavaMethod.kt b/idea/testData/quickfix/override/beforeOverridingFinalJavaMethod.kt new file mode 100644 index 00000000000..6d24ceab4da --- /dev/null +++ b/idea/testData/quickfix/override/beforeOverridingFinalJavaMethod.kt @@ -0,0 +1,5 @@ +// "Make Object.notify open" "false" +// ERROR: 'notify' in 'Object' is final and cannot be overridden +class A : Object() { + override fun notify() {} +} \ No newline at end of file diff --git a/idea/testData/quickfix/override/beforeOverridingFinalMethod.kt b/idea/testData/quickfix/override/beforeOverridingFinalMethod.kt new file mode 100644 index 00000000000..5b35e3eedcb --- /dev/null +++ b/idea/testData/quickfix/override/beforeOverridingFinalMethod.kt @@ -0,0 +1,8 @@ +// "Make A.foo open" "true" +open class A { + fun foo() {} +} + +class B : A() { + override fun foo() {} +} \ No newline at end of file diff --git a/idea/testData/quickfix/override/beforeOverridingFinalProperty.kt b/idea/testData/quickfix/override/beforeOverridingFinalProperty.kt new file mode 100644 index 00000000000..e9067c0a8c3 --- /dev/null +++ b/idea/testData/quickfix/override/beforeOverridingFinalProperty.kt @@ -0,0 +1,8 @@ +// "Make A.x open" "true" +open class A { + final var x = 42; +} + +class B : A() { + override var x = 24; +} \ No newline at end of file diff --git a/idea/testData/quickfix/override/beforeOverridingWritableAndNotWritableFinalMembers.kt b/idea/testData/quickfix/override/beforeOverridingWritableAndNotWritableFinalMembers.kt new file mode 100644 index 00000000000..9bdafadc487 --- /dev/null +++ b/idea/testData/quickfix/override/beforeOverridingWritableAndNotWritableFinalMembers.kt @@ -0,0 +1,12 @@ +// "Make 'ordinal' in E and X open" "false" +// ERROR: This type is final, so it cannot be inherited from +// ERROR: Cannot access '': it is 'private' in 'E' +// ERROR: 'ordinal' in 'E' is final and cannot be overridden +enum class E {} +trait X { + final fun ordinal() : Int = 42 +} + +class A : E(), X { + override fun ordinal() : Int = 24; +} diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java index 625e3b13acc..20d642c7891 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java @@ -711,6 +711,31 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest("idea/testData/quickfix/override/beforeNothingToOverride.kt"); } + @TestMetadata("beforeOverriddingMultipleFinalMethods.kt") + public void testOverriddingMultipleFinalMethods() throws Exception { + doTest("idea/testData/quickfix/override/beforeOverriddingMultipleFinalMethods.kt"); + } + + @TestMetadata("beforeOverridingFinalJavaMethod.kt") + public void testOverridingFinalJavaMethod() throws Exception { + doTest("idea/testData/quickfix/override/beforeOverridingFinalJavaMethod.kt"); + } + + @TestMetadata("beforeOverridingFinalMethod.kt") + public void testOverridingFinalMethod() throws Exception { + doTest("idea/testData/quickfix/override/beforeOverridingFinalMethod.kt"); + } + + @TestMetadata("beforeOverridingFinalProperty.kt") + public void testOverridingFinalProperty() throws Exception { + doTest("idea/testData/quickfix/override/beforeOverridingFinalProperty.kt"); + } + + @TestMetadata("beforeOverridingWritableAndNotWritableFinalMembers.kt") + public void testOverridingWritableAndNotWritableFinalMembers() throws Exception { + doTest("idea/testData/quickfix/override/beforeOverridingWritableAndNotWritableFinalMembers.kt"); + } + @TestMetadata("beforeParameterNameChangedAmbiguousRename.kt") public void testParameterNameChangedAmbiguousRename() throws Exception { doTest("idea/testData/quickfix/override/beforeParameterNameChangedAmbiguousRename.kt");