diff --git a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties index b27134b2125..a57637f659a 100644 --- a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties @@ -11,6 +11,8 @@ change.variable.mutability.family=Change variable mutability remove.function.body=Remove function body make.element.modifier=Make {0} {1} add.modifier=Add ''{0}'' modifier +add.supertype.modifier=Add ''{0}'' modifier to supertype +add.supertype.modifier.family=Add modifier to supertype add.star.projections=Add ''{0}'' make.element.not.modifier=Make {0} not {1} remove.modifier=Remove ''{0}'' modifier diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/FinalSupertypeFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/FinalSupertypeFix.java new file mode 100644 index 00000000000..9ebbbe9122a --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/FinalSupertypeFix.java @@ -0,0 +1,108 @@ +/* + * 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.ClassDescriptor; +import org.jetbrains.jet.lang.diagnostics.Diagnostic; +import org.jetbrains.jet.lang.psi.JetClass; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.BindingContextUtils; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lexer.JetKeywordToken; +import org.jetbrains.jet.lexer.JetToken; +import org.jetbrains.jet.plugin.JetBundle; +import org.jetbrains.jet.plugin.caches.resolve.KotlinCacheManager; + +import static org.jetbrains.jet.lexer.JetTokens.FINAL_KEYWORD; +import static org.jetbrains.jet.lexer.JetTokens.OPEN_KEYWORD; + +public class FinalSupertypeFix extends JetIntentionAction { + private final JetClass childClass; + private JetClass superClass; + + public FinalSupertypeFix(@NotNull JetClass childClass) { + super(childClass); + this.childClass = childClass; + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + if (!super.isAvailable(project, editor, file)) { + return false; + } + + BindingContext context = KotlinCacheManager.getInstance(project).getDeclarationsFromProject(project).getBindingContext(); + ClassDescriptor childClassDescriptor = context.get(BindingContext.CLASS, childClass); + if (childClassDescriptor == null) { + return false; + } + for (JetType supertype: childClassDescriptor.getTypeConstructor().getSupertypes()) { + ClassDescriptor superClassDescriptor = (ClassDescriptor) supertype.getConstructor().getDeclarationDescriptor(); + if (superClassDescriptor == null) { + continue; + } + PsiElement declaration = BindingContextUtils.descriptorToDeclaration(context, superClassDescriptor); + if (declaration instanceof JetClass) { + superClass = (JetClass) declaration; + if (!superClass.isTrait() && !superClass.isEnum() && superClass.getContainingFile().isWritable()) { + return true; + } + } + } + return false; + } + + @NotNull + @Override + public String getText() { + return JetBundle.message("add.supertype.modifier", "open"); + } + + @NotNull + @Override + public String getFamilyName() { + return JetBundle.message("add.supertype.modifier.family"); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + assert superClass != null; + JetToken[] modifiersThanCanBeReplaced = new JetKeywordToken[] { FINAL_KEYWORD }; + superClass.replace(AddModifierFix.addModifier(superClass, OPEN_KEYWORD, modifiersThanCanBeReplaced, project, false)); + } + + @NotNull + public static JetIntentionActionFactory createFactory() { + return new JetIntentionActionFactory() { + @Nullable + @Override + public IntentionAction createAction(Diagnostic diagnostic) { + JetClass childClass = QuickFixUtil.getParentElementOfType(diagnostic, JetClass.class); + return childClass == null ? null : new FinalSupertypeFix(childClass); + } + }; + } +} + diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java index fd33b49eab4..fdd19d2d5ff 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java @@ -153,5 +153,7 @@ public class QuickFixes { factories.put(WRONG_NUMBER_OF_TYPE_ARGUMENTS, AddStarProjectionsFix.createFactoryForJavaClass()); factories.put(INACCESSIBLE_OUTER_CLASS_EXPRESSION, AddModifierFix.createFactory(INNER_KEYWORD, JetClass.class)); + + factories.put(FINAL_SUPERTYPE, FinalSupertypeFix.createFactory()); } } diff --git a/idea/testData/quickfix/modifiers/finalSupertype/afterExplicitlyFinalSupertype.kt b/idea/testData/quickfix/modifiers/finalSupertype/afterExplicitlyFinalSupertype.kt new file mode 100644 index 00000000000..6687d20cab8 --- /dev/null +++ b/idea/testData/quickfix/modifiers/finalSupertype/afterExplicitlyFinalSupertype.kt @@ -0,0 +1,3 @@ +// "Add 'open' modifier to supertype" "true" +open class A {} +class B : A() {} diff --git a/idea/testData/quickfix/modifiers/finalSupertype/afterFinalSupertype.kt b/idea/testData/quickfix/modifiers/finalSupertype/afterFinalSupertype.kt new file mode 100644 index 00000000000..6687d20cab8 --- /dev/null +++ b/idea/testData/quickfix/modifiers/finalSupertype/afterFinalSupertype.kt @@ -0,0 +1,3 @@ +// "Add 'open' modifier to supertype" "true" +open class A {} +class B : A() {} diff --git a/idea/testData/quickfix/modifiers/finalSupertype/afterImplementTraitFinalSupertype.kt b/idea/testData/quickfix/modifiers/finalSupertype/afterImplementTraitFinalSupertype.kt new file mode 100644 index 00000000000..2220c574e10 --- /dev/null +++ b/idea/testData/quickfix/modifiers/finalSupertype/afterImplementTraitFinalSupertype.kt @@ -0,0 +1,6 @@ +// "Add 'open' modifier to supertype" "true" +trait X {} +trait Y {} + +open class A {} +class B : X, A(), Y {} diff --git a/idea/testData/quickfix/modifiers/finalSupertype/beforeEnumSupertype.kt b/idea/testData/quickfix/modifiers/finalSupertype/beforeEnumSupertype.kt new file mode 100644 index 00000000000..4e78e7076c2 --- /dev/null +++ b/idea/testData/quickfix/modifiers/finalSupertype/beforeEnumSupertype.kt @@ -0,0 +1,5 @@ +// "Add 'open' modifier to supertype" "false" +// ERROR: This type is final, so it cannot be inherited from +// ERROR: Cannot access '': it is 'private' in 'E' +enum class E {} +class A : E() {} diff --git a/idea/testData/quickfix/modifiers/finalSupertype/beforeExplicitlyFinalSupertype.kt b/idea/testData/quickfix/modifiers/finalSupertype/beforeExplicitlyFinalSupertype.kt new file mode 100644 index 00000000000..bc8ea06b23a --- /dev/null +++ b/idea/testData/quickfix/modifiers/finalSupertype/beforeExplicitlyFinalSupertype.kt @@ -0,0 +1,3 @@ +// "Add 'open' modifier to supertype" "true" +final class A {} +class B : A() {} diff --git a/idea/testData/quickfix/modifiers/finalSupertype/beforeFinalLibrarySupertype.kt b/idea/testData/quickfix/modifiers/finalSupertype/beforeFinalLibrarySupertype.kt new file mode 100644 index 00000000000..745a7fd8da0 --- /dev/null +++ b/idea/testData/quickfix/modifiers/finalSupertype/beforeFinalLibrarySupertype.kt @@ -0,0 +1,3 @@ +// "Add 'open' modifier to supertype" "false" +// ERROR: This type is final, so it cannot be inherited from +class A : String() {} diff --git a/idea/testData/quickfix/modifiers/finalSupertype/beforeFinalSupertype.kt b/idea/testData/quickfix/modifiers/finalSupertype/beforeFinalSupertype.kt new file mode 100644 index 00000000000..336711a5a18 --- /dev/null +++ b/idea/testData/quickfix/modifiers/finalSupertype/beforeFinalSupertype.kt @@ -0,0 +1,3 @@ +// "Add 'open' modifier to supertype" "true" +class A {} +class B : A() {} diff --git a/idea/testData/quickfix/modifiers/finalSupertype/beforeImplementTraitFinalSupertype.kt b/idea/testData/quickfix/modifiers/finalSupertype/beforeImplementTraitFinalSupertype.kt new file mode 100644 index 00000000000..519703fbabf --- /dev/null +++ b/idea/testData/quickfix/modifiers/finalSupertype/beforeImplementTraitFinalSupertype.kt @@ -0,0 +1,6 @@ +// "Add 'open' modifier to supertype" "true" +trait X {} +trait Y {} + +class A {} +class B : X, A(), Y {} diff --git a/idea/testData/quickfix/modifiers/finalSupertype/finalJavaSupertype/javaCode/testPackage/JavaClass.java b/idea/testData/quickfix/modifiers/finalSupertype/finalJavaSupertype/javaCode/testPackage/JavaClass.java new file mode 100644 index 00000000000..12108bbd62b --- /dev/null +++ b/idea/testData/quickfix/modifiers/finalSupertype/finalJavaSupertype/javaCode/testPackage/JavaClass.java @@ -0,0 +1,3 @@ +package testPackage; + +public final class JavaClass {} diff --git a/idea/testData/quickfix/modifiers/finalSupertype/finalJavaSupertype/test/FinalJavaSupertype.before.kt b/idea/testData/quickfix/modifiers/finalSupertype/finalJavaSupertype/test/FinalJavaSupertype.before.kt new file mode 100644 index 00000000000..fb9ff69075c --- /dev/null +++ b/idea/testData/quickfix/modifiers/finalSupertype/finalJavaSupertype/test/FinalJavaSupertype.before.kt @@ -0,0 +1,5 @@ +// "Add 'open' modifier to supertype" "false" +// ERROR: This type is final, so it cannot be inherited from +import testPackage.* + +class foo : JavaClass() {} diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/FinalJavaSupertypeTest.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/FinalJavaSupertypeTest.java new file mode 100644 index 00000000000..d33fdba40dd --- /dev/null +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/FinalJavaSupertypeTest.java @@ -0,0 +1,59 @@ +/* + * 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.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.testFramework.PsiTestUtil; +import org.jetbrains.jet.JetTestCaseBuilder; +import org.jetbrains.jet.testing.LocalFileSystemUtils; + +import java.util.Arrays; +import java.util.List; + +public class FinalJavaSupertypeTest extends JetQuickFixMultiFileTest { + @Override + protected void setUp() throws Exception { + super.setUp(); + + LocalFileSystemUtils.refreshPath(getTestDataPath()); + } + + public void testFinalJavaSupertype() throws Exception { + String path = getTestDataPath() + "/../javaCode/"; + final VirtualFile rootDir = PsiTestUtil.createTestProjectStructure(myProject, myModule, path, myFilesToDelete, false); + addSourceContentToRoots(myModule, rootDir); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + doTest(); + } + + @Override + protected String getCheckFileName() { + throw new IllegalStateException("This test is to check that quickfix is not available, so no check file is needed."); + } + + @Override + protected List getTestFileNames() { + return Arrays.asList(getTestName(false) + ".before.kt"); + } + + @Override + protected String getTestDataPath() { + return JetTestCaseBuilder.getHomeDirectory() + "/idea/testData/quickfix/modifiers/finalSupertype/" + getTestName(true) + "/test/"; + } +}