Added quickfix for PLATFORM_CLASS_MAPPED_TO_KOTLIN.

This commit is contained in:
Jack Zhou
2013-02-19 12:43:36 -05:00
committed by Evgeny Gerashchenko
parent cee45f6a0a
commit 738a874334
12 changed files with 443 additions and 2 deletions
@@ -126,6 +126,10 @@ move.when.else.branch.to.the.end.action=Move else branch to the end
move.when.else.branch.to.the.end.family.name=Move Else Branch to the End
change.to.property.name.family.name=Change to property name
change.to.property.name.action=Change ''{0}'' to ''{1}''
map.platform.class.to.kotlin=Change all usages of ''{0}'' in this file to ''{1}''
map.platform.class.to.kotlin.multiple=Change all usages of ''{0}'' in this file to a Kotlin class
map.platform.class.to.kotlin.advertisement=Choose an appropriate Kotlin class
map.platform.class.to.kotlin.family=Change to Kotlin class
surround.with=Surround with
surround.with.string.template="${expr}"
@@ -0,0 +1,202 @@
/*
* 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.codeInsight.template.*;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.rename.inplace.MyLookupExpression;
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.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.diagnostics.Diagnostic;
import org.jetbrains.jet.lang.diagnostics.DiagnosticWithParameters1;
import org.jetbrains.jet.lang.diagnostics.Errors;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.plugin.JetBundle;
import org.jetbrains.jet.plugin.caches.resolve.KotlinCacheManager;
import org.jetbrains.jet.renderer.DescriptorRenderer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
public class MapPlatformClassToKotlinFix extends JetIntentionAction<JetReferenceExpression> {
private static final String PRIMARY_USAGE = "PrimaryUsage";
private static final String OTHER_USAGE = "OtherUsage";
private final ClassDescriptor platformClass;
private final Collection<ClassDescriptor> possibleClasses;
public MapPlatformClassToKotlinFix(@NotNull JetReferenceExpression element, @NotNull ClassDescriptor platformClass,
@NotNull Collection<ClassDescriptor> possibleClasses) {
super(element);
this.platformClass = platformClass;
this.possibleClasses = possibleClasses;
}
@NotNull
@Override
public String getText() {
String platformClassQualifiedName = DescriptorRenderer.TEXT.renderType(platformClass.getDefaultType());
return possibleClasses.size() == 1
? JetBundle.message("map.platform.class.to.kotlin", platformClassQualifiedName,
DescriptorRenderer.TEXT.renderType(possibleClasses.iterator().next().getDefaultType()))
: JetBundle.message("map.platform.class.to.kotlin.multiple", platformClassQualifiedName);
}
@NotNull
@Override
public String getFamilyName() {
return JetBundle.message("map.platform.class.to.kotlin.family");
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
BindingContext context = KotlinCacheManager.getInstance(project).getDeclarationsFromProject().getBindingContext();
Collection<Diagnostic> diagnostics = context.getDiagnostics();
List<JetImportDirective> imports = new ArrayList<JetImportDirective>();
List<JetUserType> usages = new ArrayList<JetUserType>();
for (Diagnostic diagnostic : diagnostics) {
if (diagnostic.getFactory() != Errors.PLATFORM_CLASS_MAPPED_TO_KOTLIN) continue;
JetReferenceExpression refExpr = getImportOrUsageFromDiagnostic(diagnostic);
if (refExpr == null) continue;
DeclarationDescriptor descriptor = context.get(BindingContext.REFERENCE_TARGET, refExpr);
if (descriptor == null || !(descriptor.equals(platformClass))) continue;
JetImportDirective imp = PsiTreeUtil.getParentOfType(refExpr, JetImportDirective.class);
if (imp == null) {
JetUserType type = PsiTreeUtil.getParentOfType(refExpr, JetUserType.class);
if (type == null) continue;
usages.add(type);
} else {
imports.add(imp);
}
}
for (JetImportDirective imp : imports) {
imp.delete();
}
if (usages.isEmpty()) { // if we are not going to replace any usages, there's no reason to continue at all
return;
}
ClassDescriptor replacementClass = possibleClasses.iterator().next();
String replacementClassName = replacementClass.getName().getName();
List<PsiElement> replacedElements = new ArrayList<PsiElement>();
for (JetUserType usage : usages) {
JetTypeArgumentList typeArguments = usage.getTypeArgumentList();
String typeArgumentsString = typeArguments == null ? "" : typeArguments.getText();
JetTypeReference replacementType = JetPsiFactory.createType(project, replacementClassName + typeArgumentsString);
JetTypeElement replacementTypeElement = replacementType.getTypeElement();
assert replacementTypeElement != null;
PsiElement replacedElement = usage.replace(replacementTypeElement);
PsiElement replacedExpression = replacedElement.getFirstChild();
assert replacedExpression instanceof JetSimpleNameExpression; // assumption: the Kotlin class requires no imports
replacedElements.add(replacedExpression);
}
if (possibleClasses.size() > 1) {
LinkedHashSet<String> possibleTypes = new LinkedHashSet<String>();
for (ClassDescriptor klass : possibleClasses) {
possibleTypes.add(klass.getName().getName());
}
buildAndShowTemplate(project, editor, file, replacedElements, possibleTypes,
JetBundle.message("map.platform.class.to.kotlin.advertisement"));
}
}
private static void buildAndShowTemplate(Project project, Editor editor, PsiFile file,
Collection<PsiElement> replaceElements, LinkedHashSet<String> options, String advertisement) {
PsiDocumentManager.getInstance(project).commitAllDocuments();
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument());
PsiElement primaryReplacedExpression = replaceElements.iterator().next();
final CaretModel caretModel = editor.getCaretModel();
final int oldOffset = caretModel.getOffset();
caretModel.moveToOffset(file.getNode().getStartOffset());
TemplateBuilderImpl builder = new TemplateBuilderImpl(file);
Expression expression = new MyLookupExpression(primaryReplacedExpression.getText(), options, null, false, advertisement);
builder.replaceElement(primaryReplacedExpression, PRIMARY_USAGE, expression, true);
for (PsiElement replacedExpression : replaceElements) {
if (replacedExpression == primaryReplacedExpression) continue;
builder.replaceElement(replacedExpression, OTHER_USAGE, PRIMARY_USAGE, false);
}
TemplateManager.getInstance(project).startTemplate(editor, builder.buildInlineTemplate(), new TemplateEditingAdapter() {
@Override
public void templateFinished(Template template, boolean brokenOff) {
caretModel.moveToOffset(oldOffset);
}
});
}
private static JetReferenceExpression getImportOrUsageFromDiagnostic(@NotNull Diagnostic diagnostic) {
JetImportDirective imp = QuickFixUtil.getParentElementOfType(diagnostic, JetImportDirective.class);
JetReferenceExpression typeExpr;
if (imp == null) {
JetUserType type = QuickFixUtil.getParentElementOfType(diagnostic, JetUserType.class);
if (type == null) return null;
typeExpr = type.getReferenceExpression();
} else {
JetExpression importRef = imp.getImportedReference();
if (importRef == null || !(importRef instanceof JetDotQualifiedExpression)) return null;
JetExpression refExpr = ((JetDotQualifiedExpression) importRef).getSelectorExpression();
if (refExpr == null || !(refExpr instanceof JetReferenceExpression)) return null;
typeExpr = (JetReferenceExpression) refExpr;
}
return typeExpr;
}
public static JetIntentionActionFactory createFactory() {
return new JetIntentionActionFactory() {
@Nullable
@Override
public IntentionAction createAction(Diagnostic diagnostic) {
JetReferenceExpression typeExpr = getImportOrUsageFromDiagnostic(diagnostic);
if (typeExpr == null) return null;
Project project = diagnostic.getPsiFile().getProject();
BindingContext context = KotlinCacheManager.getInstance(project).getDeclarationsFromProject().getBindingContext();
DeclarationDescriptor descriptor = context.get(BindingContext.REFERENCE_TARGET, typeExpr);
if (descriptor == null || !(descriptor instanceof ClassDescriptor)) return null;
ClassDescriptor platformClass = (ClassDescriptor) descriptor;
assert diagnostic.getFactory() == Errors.PLATFORM_CLASS_MAPPED_TO_KOTLIN;
@SuppressWarnings("unchecked")
DiagnosticWithParameters1<JetElement, Collection<ClassDescriptor>> parametrizedDiagnostic =
(DiagnosticWithParameters1<JetElement, Collection<ClassDescriptor>>) diagnostic;
return new MapPlatformClassToKotlinFix(typeExpr, platformClass, parametrizedDiagnostic.getA());
}
};
}
}
@@ -204,8 +204,10 @@ public class QuickFixes {
factories.put(NOT_AN_ANNOTATION_CLASS, MakeClassAnAnnotationClassFix.createFactory());
factories.put(DANGLING_FUNCTION_LITERAL_ARGUMENT_SUSPECTED, AddSemicolonAfterFunctionCallFix.createFactory());
factories.put(RETURN_TYPE_MISMATCH_ON_OVERRIDE, ChangeReturnTypeToMatchOverriddenMethodFix.createFactory());
factories.put(PROPERTY_TYPE_MISMATCH_ON_OVERRIDE, ChangePropertyTypeToMatchOverriddenPropertyFix.createFactory());
factories.put(PLATFORM_CLASS_MAPPED_TO_KOTLIN, MapPlatformClassToKotlinFix.createFactory());
}
}
@@ -0,0 +1,24 @@
// "Change all usages of 'java.lang.Comparable<T>' in this file to 'jet.Comparable<T>'" "true"
import java.lang.*
import java.lang
import java.lang as jl
fun <T> a() : Comparable<T>? {
return null
}
fun b() : Comparable<String> {
throw Exception()
}
fun c() : Comparable<String> {
throw Exception()
}
fun d() : Comparable<String>? {
return null
}
fun e() : Comparable<String>? {
throw Exception()
}
@@ -0,0 +1,24 @@
// "Change all usages of 'java.lang.Comparable<T>' in this file to 'jet.Comparable<T>'" "true"
import java.lang.*
import java.lang
import java.lang as jl
fun <T> a() : Comparable<T>? {
return null
}
fun b() : Comparable<String> {
throw Exception()
}
fun c() : Comparable<String> {
throw Exception()
}
fun d() : Comparable<String>? {
return null
}
fun e() : Comparable<String>? {
throw Exception()
}
@@ -0,0 +1,24 @@
// "Change all usages of 'java.lang.Comparable<T>' in this file to 'jet.Comparable<T>'" "true"
import java.lang.*
import java.lang
import java.lang as jl
fun <T> a() : Comparable<T>? {
return null
}
fun b() : Comparable<String> {
throw Exception()
}
fun c() : Comparable<String> {
throw Exception()
}
fun d() : Comparable<String>? {
return null
}
fun e() : Comparable<String>? {
throw Exception()
}
@@ -0,0 +1,24 @@
// "Change all usages of 'java.lang.Iterable<T>' in this file to a Kotlin class" "true"
import java.lang.*
import java.lang
import java.lang as jl
fun <T> a() : Iterable<T>? {
return null
}
fun b() : Iterable<String>? {
return null
}
fun c() : Iterable<String> {
throw Exception()
}
fun d() : Iterable<String>? {
return null
}
fun e() : Iterable<String>? {
throw Exception()
}
@@ -0,0 +1,27 @@
// "Change all usages of 'java.lang.Comparable<T>' in this file to 'jet.Comparable<T>'" "true"
import java.lang.*
import java.lang.Comparable
import java.lang.Comparable
import java.lang.Comparable as Foo
import java.lang
import java.lang as jl
fun <T> a() : java.lang.Comparable<T><caret>? {
return null
}
fun b() : lang.Comparable<String> {
throw Exception()
}
fun c() : Foo<String> {
throw Exception()
}
fun d() : jl.Comparable<String>? {
return null
}
fun e() : Comparable<String>? {
throw Exception()
}
@@ -0,0 +1,27 @@
// "Change all usages of 'java.lang.Comparable<T>' in this file to 'jet.Comparable<T>'" "true"
import java.lang.*
import java.lang.Comparable
import java.lang.Comparable
import java.lang.Comparable as Foo
import java.lang
import java.lang as jl
fun <T> a() : java.lang.Comparable<T>? {
return null
}
fun b() : lang.Comparable<String> {
throw Exception()
}
fun c() : Foo<String> {
throw Exception()
}
fun d() : jl.Comparable<String><caret>? {
return null
}
fun e() : Comparable<String>? {
throw Exception()
}
@@ -0,0 +1,27 @@
// "Change all usages of 'java.lang.Comparable<T>' in this file to 'jet.Comparable<T>'" "true"
import java.lang.*
import java.lang.Comparable
import java.lang.Comparable
import java.lang.Comparable<caret> as Foo
import java.lang
import java.lang as jl
fun <T> a() : java.lang.Comparable<T>? {
return null
}
fun b() : lang.Comparable<String> {
throw Exception()
}
fun c() : Foo<String> {
throw Exception()
}
fun d() : jl.Comparable<String>? {
return null
}
fun e() : Comparable<String>? {
throw Exception()
}
@@ -0,0 +1,27 @@
// "Change all usages of 'java.lang.Iterable<T>' in this file to a Kotlin class" "true"
import java.lang.*
import java.lang.Iterable
import java.lang.Iterable
import java.lang.Iterable as Foo
import java.lang
import java.lang as jl
fun <T> a() : java.lang.Iterable<T>? {
return null
}
fun b() : lang.Iterable<String>? {
return null
}
fun c() : Foo<String><caret> {
throw Exception()
}
fun d() : jl.Iterable<String>? {
return null
}
fun e() : Iterable<String>? {
throw Exception()
}
@@ -31,7 +31,7 @@ import org.jetbrains.jet.plugin.quickfix.AbstractQuickFixTest;
/** This class is generated by {@link org.jetbrains.jet.generators.tests.GenerateTests}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/quickfix")
@InnerTestClasses({QuickFixTestGenerated.Abstract.class, QuickFixTestGenerated.AddStarProjections.class, QuickFixTestGenerated.AutoImports.class, QuickFixTestGenerated.CheckArguments.class, QuickFixTestGenerated.Expressions.class, QuickFixTestGenerated.Migration.class, QuickFixTestGenerated.Modifiers.class, QuickFixTestGenerated.Nullables.class, QuickFixTestGenerated.Override.class, QuickFixTestGenerated.SupertypeInitialization.class, QuickFixTestGenerated.TypeAddition.class, QuickFixTestGenerated.TypeImports.class, QuickFixTestGenerated.TypeProjection.class, QuickFixTestGenerated.UselessImports.class, QuickFixTestGenerated.Variables.class, QuickFixTestGenerated.When.class})
@InnerTestClasses({QuickFixTestGenerated.Abstract.class, QuickFixTestGenerated.AddStarProjections.class, QuickFixTestGenerated.AutoImports.class, QuickFixTestGenerated.CheckArguments.class, QuickFixTestGenerated.Expressions.class, QuickFixTestGenerated.Migration.class, QuickFixTestGenerated.Modifiers.class, QuickFixTestGenerated.Nullables.class, QuickFixTestGenerated.Override.class, QuickFixTestGenerated.PlatformClasses.class, QuickFixTestGenerated.SupertypeInitialization.class, QuickFixTestGenerated.TypeAddition.class, QuickFixTestGenerated.TypeImports.class, QuickFixTestGenerated.TypeProjection.class, QuickFixTestGenerated.UselessImports.class, QuickFixTestGenerated.Variables.class, QuickFixTestGenerated.When.class})
public class QuickFixTestGenerated extends AbstractQuickFixTest {
public void testAllFilesPresentInQuickfix() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/quickfix"), Pattern.compile("^before(\\w+)\\.kt$"), true);
@@ -807,6 +807,34 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
@TestMetadata("idea/testData/quickfix/platformClasses")
public static class PlatformClasses extends AbstractQuickFixTest {
public void testAllFilesPresentInPlatformClasses() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/quickfix/platformClasses"), Pattern.compile("^before(\\w+)\\.kt$"), true);
}
@TestMetadata("beforeMapPlatformClassToKotlin1.kt")
public void testMapPlatformClassToKotlin1() throws Exception {
doTest("idea/testData/quickfix/platformClasses/beforeMapPlatformClassToKotlin1.kt");
}
@TestMetadata("beforeMapPlatformClassToKotlin2.kt")
public void testMapPlatformClassToKotlin2() throws Exception {
doTest("idea/testData/quickfix/platformClasses/beforeMapPlatformClassToKotlin2.kt");
}
@TestMetadata("beforeMapPlatformClassToKotlin3.kt")
public void testMapPlatformClassToKotlin3() throws Exception {
doTest("idea/testData/quickfix/platformClasses/beforeMapPlatformClassToKotlin3.kt");
}
@TestMetadata("beforeMapPlatformClassToKotlin4.kt")
public void testMapPlatformClassToKotlin4() throws Exception {
doTest("idea/testData/quickfix/platformClasses/beforeMapPlatformClassToKotlin4.kt");
}
}
@TestMetadata("idea/testData/quickfix/supertypeInitialization")
public static class SupertypeInitialization extends AbstractQuickFixTest {
public void testAllFilesPresentInSupertypeInitialization() throws Exception {
@@ -1132,6 +1160,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
suite.addTest(Modifiers.innerSuite());
suite.addTest(Nullables.innerSuite());
suite.addTest(Override.innerSuite());
suite.addTestSuite(PlatformClasses.class);
suite.addTestSuite(SupertypeInitialization.class);
suite.addTestSuite(TypeAddition.class);
suite.addTestSuite(TypeImports.class);