'Specify explicit type' action refactoring

This commit is contained in:
Mikhael Bogdanov
2013-02-18 18:31:59 +04:00
parent d8ea6a3a59
commit 5460f984e9
12 changed files with 259 additions and 62 deletions
@@ -63,7 +63,7 @@ public class JetPsiFactory {
}
//the pair contains the first and the last elements of a range
public static Pair<PsiElement, PsiElement> createColon(Project project) {
public static Pair<PsiElement, PsiElement> createColonAndWhiteSpaces(Project project) {
JetProperty property = createProperty(project, "val x : Int");
return Pair.create(property.findElementAt(5), property.findElementAt(7));
}
@@ -0,0 +1,100 @@
/*
* 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.intentions;
import com.intellij.codeInsight.completion.InsertHandler;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInsight.template.Expression;
import com.intellij.codeInsight.template.ExpressionContext;
import com.intellij.codeInsight.template.Result;
import com.intellij.codeInsight.template.TextResult;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.codeInsight.template.impl.TemplateState;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import java.util.Iterator;
import java.util.List;
public abstract class JetTypeLookupExpression<T> extends Expression {
protected final LookupElement[] lookupItems;
protected final T defaultItem;
private final String advertisementText;
public JetTypeLookupExpression(
List<T> lookupItems,
T defaultItem,
String advertisement
) {
this.advertisementText = advertisement;
this.defaultItem = defaultItem;
this.lookupItems = initLookupItems(lookupItems);
}
private LookupElement[] initLookupItems(List<T> lookupItems) {
LookupElement[] lookupElements = new LookupElement[lookupItems.size()];
Iterator<T> iterator = lookupItems.iterator();
for (int i = 0; i < lookupElements.length; i++) {
final T suggestion = iterator.next();
lookupElements[i] = LookupElementBuilder.create(suggestion, getLookupString(suggestion)).withInsertHandler(
new InsertHandler<LookupElement>() {
@Override
public void handleInsert(InsertionContext context, LookupElement item) {
Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(context.getEditor());
TemplateState templateState = TemplateManagerImpl.getTemplateState(topLevelEditor);
if (templateState != null) {
TextRange range = templateState.getCurrentVariableRange();
if (range != null) {
topLevelEditor.getDocument()
.replaceString(range.getStartOffset(), range.getEndOffset(), getResult((T) item.getObject()));
}
}
}
});
}
return lookupElements;
}
public LookupElement[] calculateLookupItems(ExpressionContext context) {
return lookupItems.length > 1 ? lookupItems : null;
}
public Result calculateQuickResult(ExpressionContext context) {
return calculateResult(context);
}
public Result calculateResult(ExpressionContext context) {
return new TextResult(getLookupString(defaultItem));
}
@Override
public String getAdvertisingText() {
return advertisementText;
}
protected abstract String getLookupString(T element);
protected abstract String getResult(T element);
}
@@ -16,18 +16,25 @@
package org.jetbrains.jet.plugin.intentions;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.lang.ASTNode;
import com.intellij.codeInsight.template.Expression;
import com.intellij.codeInsight.template.Template;
import com.intellij.codeInsight.template.TemplateBuilderImpl;
import com.intellij.codeInsight.template.TemplateEditingAdapter;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.JetNodeTypes;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor;
import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
import org.jetbrains.jet.lang.diagnostics.Diagnostic;
import org.jetbrains.jet.lang.diagnostics.Errors;
import org.jetbrains.jet.lang.psi.*;
@@ -35,12 +42,14 @@ import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.types.ErrorUtils;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.TypeConstructor;
import org.jetbrains.jet.lang.types.TypeUtils;
import org.jetbrains.jet.plugin.JetBundle;
import org.jetbrains.jet.plugin.codeInsight.ReferenceToClassesShortening;
import org.jetbrains.jet.plugin.project.AnalyzeSingleFileUtil;
import org.jetbrains.jet.renderer.DescriptorRenderer;
import java.util.Collections;
import java.util.*;
public class SpecifyTypeExplicitlyAction extends PsiElementBaseIntentionAction {
@NotNull
@@ -61,7 +70,7 @@ public class SpecifyTypeExplicitlyAction extends PsiElementBaseIntentionAction {
if (parent instanceof JetProperty) {
JetProperty property = (JetProperty) parent;
if (property.getTypeRef() == null) {
addTypeAnnotation(project, property, type);
addTypeAnnotation(project, editor, property, type);
}
else {
removeTypeAnnotation(property);
@@ -70,7 +79,7 @@ public class SpecifyTypeExplicitlyAction extends PsiElementBaseIntentionAction {
else if (parent instanceof JetParameter) {
JetParameter parameter = (JetParameter) parent;
if (parameter.getTypeReference() == null) {
addTypeAnnotation(project, parameter, type);
addTypeAnnotation(project, editor, parameter, type);
}
else {
removeTypeAnnotation(parameter);
@@ -79,7 +88,7 @@ public class SpecifyTypeExplicitlyAction extends PsiElementBaseIntentionAction {
else if (parent instanceof JetNamedFunction) {
JetNamedFunction function = (JetNamedFunction) parent;
assert function.getReturnTypeRef() == null;
addTypeAnnotation(project, function, type);
addTypeAnnotation(project, editor, function, type);
}
else {
throw new IllegalStateException("Unexpected parent: " + parent);
@@ -160,58 +169,100 @@ public class SpecifyTypeExplicitlyAction extends PsiElementBaseIntentionAction {
return type == null ? ErrorUtils.createErrorType("null type") : type;
}
public static void addTypeAnnotation(Project project, JetProperty property, @NotNull JetType exprType) {
if (property.getTypeRef() != null) return;
public static void addTypeAnnotation(
@NotNull Project project,
@NotNull Editor editor,
@NotNull JetProperty property,
@NotNull JetType exprType
) {
if (property.getTypeRef() != null) {
return;
}
PsiElement anchor = property.getNameIdentifier();
if (anchor == null) return;
anchor = anchor.getNextSibling();
if (anchor != null) {
if (!(anchor instanceof PsiWhiteSpace)) {
return;
}
}
// For enum entry expression, we want type "MyEntry" instead of "MyEntry.<class-object>.ENTRY1"
ClassifierDescriptor typeClassifier = exprType.getConstructor().getDeclarationDescriptor();
assert typeClassifier != null;
if (DescriptorUtils.isEnumEntry(typeClassifier)) {
ClassDescriptor enumClass = (ClassDescriptor) typeClassifier.getContainingDeclaration().getContainingDeclaration();
assert enumClass != null;
exprType = enumClass.getDefaultType();
}
JetTypeReference typeReference = JetPsiFactory.createType(project, DescriptorRenderer.TEXT.renderType(exprType));
ASTNode colon = JetPsiFactory.createColonNode(project);
ASTNode anchorNode = anchor == null ? null : anchor.getNode().getTreeNext();
if (anchor == null) {
property.getNode().addChild(JetPsiFactory.createWhiteSpace(project).getNode(), anchorNode);
return;
}
property.getNode().addChild(colon, anchorNode);
property.getNode().addChild(JetPsiFactory.createWhiteSpace(project).getNode(), anchorNode);
property.getNode().addChild(typeReference.getNode(), anchorNode);
property.getNode().addChild(JetPsiFactory.createWhiteSpace(project).getNode(), anchorNode);
if (anchor != null) {
anchor.delete();
}
ReferenceToClassesShortening.compactReferenceToClasses(Collections.singletonList(property));
addTypeAnnotation(project, editor, property, anchor, exprType);
}
public static void addTypeAnnotation(Project project, JetFunction function, @NotNull JetType exprType) {
JetTypeReference typeReference = JetPsiFactory.createType(project, DescriptorRenderer.TEXT.renderType(exprType));
Pair<PsiElement, PsiElement> colon = JetPsiFactory.createColon(project);
public static void addTypeAnnotation(Project project, Editor editor, JetFunction function, @NotNull JetType exprType) {
JetParameterList valueParameterList = function.getValueParameterList();
assert valueParameterList != null;
function.addAfter(typeReference, valueParameterList);
function.addRangeAfter(colon.getFirst(), colon.getSecond(), valueParameterList);
ReferenceToClassesShortening.compactReferenceToClasses(Collections.singletonList(function));
addTypeAnnotation(project, editor, function, valueParameterList, exprType);
}
public static void addTypeAnnotation(Project project, JetParameter parameter, @NotNull JetType exprType) {
JetTypeReference typeReference = JetPsiFactory.createType(project, DescriptorRenderer.TEXT.renderType(exprType));
Pair<PsiElement, PsiElement> colon = JetPsiFactory.createColon(project);
parameter.addAfter(typeReference, parameter.getNameIdentifier());
parameter.addRangeAfter(colon.getFirst(), colon.getSecond(), parameter.getNameIdentifier());
ReferenceToClassesShortening.compactReferenceToClasses(Collections.singletonList(parameter));
public static void addTypeAnnotation(Project project, Editor editor, JetParameter parameter, @NotNull JetType exprType) {
addTypeAnnotation(project, editor, parameter, parameter.getNameIdentifier(), exprType);
}
private static void addTypeAnnotation(
@NotNull Project project,
@NotNull Editor editor,
@NotNull final JetNamedDeclaration namedDeclaration,
@NotNull PsiElement anchor,
@NotNull JetType exprType
) {
TypeConstructor constructor = exprType.getConstructor();
boolean isAnonymous = DescriptorUtils.isAnonymous(constructor.getDeclarationDescriptor());
Set<JetType> allSupertypes = TypeUtils.getAllSupertypes(exprType);
List<JetType> types = isAnonymous ? new ArrayList<JetType>() : Lists.newArrayList(exprType);
types.addAll(allSupertypes);
Expression expression = new JetTypeLookupExpression<JetType>(
types,
types.iterator().next(),
JetBundle.message("specify.type.explicitly.add.action.name")
) {
@Override
protected String getLookupString(JetType element) {
return DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(element);
}
@Override
protected String getResult(JetType element) {
return DescriptorRenderer.TEXT.renderType(element);
}
};
JetTypeReference typeReference = JetPsiFactory.createType(project, "Any");
namedDeclaration.addAfter(typeReference, anchor);
Pair<PsiElement, PsiElement> colon = JetPsiFactory.createColonAndWhiteSpaces(project);
namedDeclaration.addRangeAfter(colon.getFirst(), colon.getSecond(), anchor);
PsiDocumentManager.getInstance(project).commitAllDocuments();
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument());
JetTypeReference newTypeRef = getTypeRef(namedDeclaration);
TemplateBuilderImpl builder = new TemplateBuilderImpl(newTypeRef);
builder.replaceElement(newTypeRef, expression);
editor.getCaretModel().moveToOffset(newTypeRef.getNode().getStartOffset());
TemplateManagerImpl manager = new TemplateManagerImpl(project);
manager.startTemplate(editor, builder.buildInlineTemplate(), new TemplateEditingAdapter() {
@Override
public void templateFinished(Template template, boolean brokenOff) {
ReferenceToClassesShortening.compactReferenceToClasses(Collections.singletonList(namedDeclaration));
}
});
}
@Nullable
private static JetTypeReference getTypeRef(@NotNull JetNamedDeclaration namedDeclaration) {
if (namedDeclaration instanceof JetProperty) {
return ((JetProperty) namedDeclaration).getTypeRef();
}
else if (namedDeclaration instanceof JetParameter) {
return ((JetParameter) namedDeclaration).getTypeReference();
}
else if (namedDeclaration instanceof JetFunction) {
return ((JetFunction) namedDeclaration).getReturnTypeRef();
}
assert false : "Wrong namedDeclaration: " + namedDeclaration.getText();
return null;
}
private static void removeTypeAnnotation(@NotNull JetNamedDeclaration property, @Nullable JetTypeReference typeReference) {
@@ -173,6 +173,7 @@ public class QuickFixes {
factories.put(PACKAGE_MEMBER_CANNOT_BE_PROTECTED, removeProtectedModifierFactory);
actions.put(PUBLIC_MEMBER_SHOULD_SPECIFY_TYPE, new SpecifyTypeExplicitlyFix());
actions.put(AMBIGUOUS_ANONYMOUS_TYPE_INFERRED, new SpecifyTypeExplicitlyFix());
factories.put(ELSE_MISPLACED_IN_WHEN, MoveWhenElseBranchFix.createFactory());
factories.put(NO_ELSE_IN_WHEN, AddWhenElseBranchFix.createFactory());
@@ -34,14 +34,14 @@ public class RemovePartsFromPropertyFix extends JetIntentionAction<JetProperty>
private final boolean removeInitializer;
private final boolean removeGetter;
private final boolean removeSetter;
private RemovePartsFromPropertyFix(@NotNull JetProperty element, boolean removeInitializer, boolean removeGetter, boolean removeSetter) {
super(element);
this.removeInitializer = removeInitializer;
this.removeGetter = removeGetter;
this.removeSetter = removeSetter;
}
private RemovePartsFromPropertyFix(@NotNull JetProperty element) {
this(element, element.getInitializer() != null,
element.getGetter() != null && element.getGetter().getBodyExpression() != null,
@@ -117,7 +117,7 @@ public class RemovePartsFromPropertyFix extends JetIntentionAction<JetProperty>
}
element = (JetProperty) element.replace(newElement);
if (typeToAdd != null) {
SpecifyTypeExplicitlyAction.addTypeAnnotation(project, element, typeToAdd);
SpecifyTypeExplicitlyAction.addTypeAnnotation(project, editor, element, typeToAdd);
}
}
@@ -46,10 +46,10 @@ public class SpecifyTypeExplicitlyFix extends PsiElementBaseIntentionAction {
JetNamedDeclaration declaration = PsiTreeUtil.getParentOfType(element, JetProperty.class, JetNamedFunction.class);
JetType type = getTypeForDeclaration(declaration);
if (declaration instanceof JetProperty) {
addTypeAnnotation(project, (JetProperty) declaration, type);
addTypeAnnotation(project, editor, (JetProperty) declaration, type);
}
else if (declaration instanceof JetNamedFunction) {
addTypeAnnotation(project, (JetNamedFunction) declaration, type);
addTypeAnnotation(project, editor, (JetNamedFunction) declaration, type);
}
else {
assert false : "Couldn't find property or function";
@@ -16,8 +16,6 @@
package org.jetbrains.jet.plugin.refactoring.introduceVariable;
import com.intellij.codeInsight.template.Template;
import com.intellij.codeInsight.template.TemplateManager;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.codeInsight.template.impl.TemplateState;
import com.intellij.lang.ASTNode;
@@ -25,9 +23,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
@@ -35,7 +31,6 @@ import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer;
import com.intellij.ui.NonFocusableCheckBox;
import org.jetbrains.annotations.Nullable;
@@ -127,7 +122,7 @@ public class JetInplaceVariableIntroducer extends InplaceVariableIntroducer<JetE
}
}
}
SpecifyTypeExplicitlyAction.addTypeAnnotation(myProject, myProperty, myExprType);
SpecifyTypeExplicitlyAction.addTypeAnnotation(myProject, myEditor, myProperty, myExprType);
PsiDocumentManager.getInstance(myProject).commitDocument(myEditor.getDocument());
TemplateState templateState = TemplateManagerImpl.getTemplateState(myEditor);
if (templateState != null) {
@@ -0,0 +1,10 @@
// "Specify return type explicitly" "true"
package a
trait A {}
trait B {}
class C {
fun property(): B<caret> = object : B, A {}
}
@@ -0,0 +1,10 @@
// "Specify type explicitly" "true"
package a
trait A {}
trait B {}
class C {
val property: B<caret> = object : B, A {}
}
@@ -0,0 +1,10 @@
// "Specify return type explicitly" "true"
package a
trait A {}
trait B {}
class C {
fun <caret>property() = object : B, A {}
}
@@ -0,0 +1,10 @@
// "Specify type explicitly" "true"
package a
trait A {}
trait B {}
class C {
val <caret>property = object : B, A {}
}
@@ -693,6 +693,16 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/quickfix/typeAddition"), Pattern.compile("^before(\\w+)\\.kt$"), true);
}
@TestMetadata("beforeAmbiguousFunctionReturnType.kt")
public void testAmbiguousFunctionReturnType() throws Exception {
doTest("idea/testData/quickfix/typeAddition/beforeAmbiguousFunctionReturnType.kt");
}
@TestMetadata("beforeAmbiguousPropertyReturnType.kt")
public void testAmbiguousPropertyReturnType() throws Exception {
doTest("idea/testData/quickfix/typeAddition/beforeAmbiguousPropertyReturnType.kt");
}
@TestMetadata("beforeNoAddErrorType.kt")
public void testNoAddErrorType() throws Exception {
doTest("idea/testData/quickfix/typeAddition/beforeNoAddErrorType.kt");