Groovy: 'normalize implements/extends lists'

This commit is contained in:
Max Medvedev
2012-10-14 20:34:47 +03:00
parent 3a1f629c94
commit 9c23752f97
7 changed files with 161 additions and 330 deletions
@@ -318,22 +318,6 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator {
checkConstructors(myHolder, typeDefinition);
}
private static void checkReferenceList(AnnotationHolder holder,
GrReferenceList list,
boolean interfaceExpected,
String message,
@Nullable IntentionAction fix) {
if (list == null) return;
for (GrCodeReferenceElement refElement : list.getReferenceElements()) {
final PsiElement psiClass = refElement.resolve();
if (psiClass instanceof PsiClass && ((PsiClass)psiClass).isInterface() != interfaceExpected) {
if (fix != null) {
holder.createErrorAnnotation(refElement, message).registerFix(fix);
}
}
}
}
private static void checkConstructors(AnnotationHolder holder, GrTypeDefinition typeDefinition) {
if (typeDefinition.isEnum() || typeDefinition.isInterface() || typeDefinition.isAnonymous()) return;
final PsiClass superClass = typeDefinition.getSuperClass();
@@ -1329,6 +1313,49 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator {
checkAnnotationList(myHolder, importStatement.getAnnotationList(), GroovyBundle.message("import.statement.cannot.have.modifiers"));
}
@Override
public void visitExtendsClause(GrExtendsClause extendsClause) {
GrTypeDefinition typeDefinition = (GrTypeDefinition)extendsClause.getParent();
if (typeDefinition.isInterface()) {
checkReferenceList(myHolder, extendsClause, true, GroovyBundle.message("no.class.expected.here"), null);
}
else {
checkReferenceList(myHolder, extendsClause, false, GroovyBundle.message("no.interface.expected.here"), new ChangeExtendsImplementsQuickFix(typeDefinition));
}
checkForWildCards(myHolder, extendsClause);
}
@Override
public void visitImplementsClause(GrImplementsClause implementsClause) {
GrTypeDefinition typeDefinition = (GrTypeDefinition)implementsClause.getParent();
if (typeDefinition.isInterface()) {
myHolder.createErrorAnnotation(implementsClause, GroovyBundle.message("no.implements.clause.allowed.for.interface"));
}
else {
checkReferenceList(myHolder, implementsClause, true, GroovyBundle.message("no.class.expected.here"), new ChangeExtendsImplementsQuickFix(typeDefinition));
}
checkForWildCards(myHolder, implementsClause);
}
private static void checkReferenceList(@NotNull AnnotationHolder holder,
@NotNull GrReferenceList list,
boolean interfaceExpected,
@NotNull String message,
@Nullable IntentionAction fix) {
for (GrCodeReferenceElement refElement : list.getReferenceElements()) {
final PsiElement psiClass = refElement.resolve();
if (psiClass instanceof PsiClass && ((PsiClass)psiClass).isInterface() != interfaceExpected) {
if (fix != null) {
holder.createErrorAnnotation(refElement, message).registerFix(fix);
}
}
}
}
private static void checkFlowInterruptStatement(GrFlowInterruptingStatement statement, AnnotationHolder holder) {
final PsiElement label = statement.getLabelIdentifier();
@@ -1669,30 +1696,6 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator {
}
}
final GrImplementsClause implementsClause = typeDefinition.getImplementsClause();
final GrExtendsClause extendsClause = typeDefinition.getExtendsClause();
if (typeDefinition.isInterface()) {
checkReferenceList(holder, extendsClause, true, GroovyBundle.message("no.interface.expected.here"), null);
if (implementsClause != null) {
holder.createErrorAnnotation(implementsClause, GroovyBundle.message("no.implements.clause.allowed.for.interface"));
}
}
else {
checkReferenceList(holder, extendsClause, false, GroovyBundle.message("no.interface.expected.here"),
ExtendsImplementsFix.MOVE_TO_IMPLEMENTS_LIST);
checkReferenceList(holder, implementsClause, true, GroovyBundle.message("no.class.expected.here"),
ExtendsImplementsFix.MOVE_TO_EXTENDS_LIST);
}
if (extendsClause != null) {
checkForExtendingInterface(holder, extendsClause, implementsClause, ((GrTypeDefinition)extendsClause.getParent()));
}
checkForWildCards(holder, extendsClause);
checkForWildCards(holder, implementsClause);
checkDuplicateClass(typeDefinition, holder);
checkCyclicInheritance(holder, typeDefinition);
@@ -1758,8 +1761,8 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator {
}
final String qName = typeDefinition.getQualifiedName();
if (qName != null) {
final PsiClass[] classes =
JavaPsiFacade.getInstance(typeDefinition.getProject()).findClasses(qName, typeDefinition.getResolveScope());
JavaPsiFacade facade = JavaPsiFacade.getInstance(typeDefinition.getProject());
final PsiClass[] classes = facade.findClasses(qName, typeDefinition.getResolveScope());
if (classes.length > 1) {
String packageName = getPackageName(typeDefinition);
@@ -1789,22 +1792,6 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator {
return allClasses.length == 2 && (allClasses[0] instanceof GroovyScriptClass || allClasses[1] instanceof GroovyScriptClass);
}
private static void checkForExtendingInterface(AnnotationHolder holder,
GrExtendsClause extendsClause,
GrImplementsClause implementsClause,
GrTypeDefinition myClass) {
for (GrCodeReferenceElement ref : extendsClause.getReferenceElements()) {
final PsiElement clazz = ref.resolve();
if (clazz == null) continue;
if (myClass.isInterface() && clazz instanceof PsiClass && !((PsiClass)clazz).isInterface()) {
final Annotation annotation = holder.createErrorAnnotation(ref, GroovyBundle.message("class.is.not.expected.here"));
annotation.registerFix(new ChangeExtendsImplementsQuickFix(extendsClause, implementsClause));
}
}
}
public static class DuplicateVariablesProcessor extends PropertyResolverProcessor {
private boolean myBorderPassed;
private final boolean myHasVisibilityModifier;
@@ -16,13 +16,11 @@
package org.jetbrains.plugins.groovy.annotator.intentions;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -35,8 +33,9 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrImplements
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
/**
* User: Dmitry.Krasilschikov
@@ -47,19 +46,13 @@ public class ChangeExtendsImplementsQuickFix implements IntentionAction {
private final GrExtendsClause myExtendsClause;
@Nullable
private final GrImplementsClause myImplementsClause;
@NotNull
private final GrTypeDefinition myClass;
final GrTypeDefinition myClass;
public ChangeExtendsImplementsQuickFix(GrExtendsClause extendsClause, GrImplementsClause implementsClause) {
myExtendsClause = extendsClause;
myImplementsClause = implementsClause;
if (myImplementsClause != null) {
myClass = (GrTypeDefinition) myImplementsClause.getParent();
} else {
assert myExtendsClause != null;
myClass = (GrTypeDefinition) myExtendsClause.getParent();
}
public ChangeExtendsImplementsQuickFix(@NotNull GrTypeDefinition aClass) {
myClass = aClass;
myExtendsClause = aClass.getExtendsClause();
myImplementsClause = aClass.getImplementsClause();
}
@NotNull
@@ -73,113 +66,82 @@ public class ChangeExtendsImplementsQuickFix implements IntentionAction {
}
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return myClass != null && myClass.isValid() && myClass.getManager().isInProject(file);
return myClass.isValid() && myClass.getManager().isInProject(file);
}
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
GrCodeReferenceElement[] extendsReferenceElements = GrCodeReferenceElement.EMPTY_ARRAY;
GrCodeReferenceElement[] implementsReferenceElements = GrCodeReferenceElement.EMPTY_ARRAY;
Set<String> classes = new LinkedHashSet<String>();
Set<String> interfaces = new LinkedHashSet<String>();
Set<String> unknownClasses = new LinkedHashSet<String>();
Set<String> unknownInterfaces = new LinkedHashSet<String>();
if (myExtendsClause != null) {
extendsReferenceElements = myExtendsClause.getReferenceElements();
collectRefs(myExtendsClause.getReferenceElements(), classes, interfaces, myClass.isInterface() ? unknownInterfaces : unknownClasses);
myExtendsClause.delete();
}
if (myImplementsClause != null) {
implementsReferenceElements = myImplementsClause.getReferenceElements();
collectRefs(myImplementsClause.getReferenceElements(), classes, interfaces, unknownInterfaces);
myImplementsClause.delete();
}
Set<String> classes = new TreeSet<String>();
Set<String> interfaces = new TreeSet<String>();
for (GrCodeReferenceElement extendsReferenceElement : extendsReferenceElements) {
final PsiElement extendsElement = extendsReferenceElement.resolve();
if (!(extendsElement instanceof PsiClass)) continue;
if (myClass.isInterface() && !((PsiClass) extendsElement).isInterface()) continue;
if (myClass.isInterface()) {
if (((PsiClass) extendsElement).isInterface()) {
classes.add(extendsReferenceElement.getCanonicalText());
}
} else {
if (((PsiClass) extendsElement).isInterface()) {
interfaces.add(extendsReferenceElement.getCanonicalText());
} else {
classes.add(extendsReferenceElement.getCanonicalText());
}
}
// if (((PsiClass) extendsElement).isInterface()) {
// interfaces.add(extendsReferenceElement.getCanonicalText());
// } else {
// classes.add(extendsReferenceElement.getCanonicalText());
// }
if (myClass.isInterface()) {
interfaces.addAll(classes);
unknownInterfaces.addAll(unknownClasses);
addNewClause(interfaces, unknownInterfaces, project, true);
}
for (GrCodeReferenceElement implementsReferenceElement : implementsReferenceElements) {
//interface cannot implement anything
final PsiElement implementsElement = implementsReferenceElement.resolve();
if (!(implementsElement instanceof PsiClass)) continue;
if (myClass.isInterface()) {
if (((PsiClass) implementsElement).isInterface()) {
classes.add(implementsReferenceElement.getCanonicalText());
}
} else {
if (((PsiClass) implementsElement).isInterface()) {
interfaces.add(implementsReferenceElement.getCanonicalText());
} else {
classes.add(implementsReferenceElement.getCanonicalText());
}
}
else {
addNewClause(classes, unknownClasses, project, true);
addNewClause(interfaces, unknownInterfaces, project, false);
}
if (myExtendsClause != null) {
final ASTNode extendsClauseNode = myExtendsClause.getNode();
extendsClauseNode.getTreeParent().removeChild(extendsClauseNode);
}
if (myImplementsClause != null) {
final ASTNode implClauseNode = myImplementsClause.getNode();
implClauseNode.getTreeParent().removeChild(implClauseNode);
}
if (!classes.isEmpty()) {
addNewClause(classes, project, true);
}
if (!interfaces.isEmpty()) {
addNewClause(interfaces, project, false);
}
CodeStyleManager.getInstance(project).reformatText(myClass.getContainingFile(),
myClass.getTextRange().getStartOffset(), myClass.getBody().getTextOffset() + 2);
}
private void addNewClause(Set<String> elements, Project project, boolean isExtends) throws IncorrectOperationException {
String classText = "class A " + (isExtends ? "extends " : "implements ");
private static void collectRefs(GrCodeReferenceElement[] refs, Collection<String> classes, Collection<String> interfaces, Collection<String> unknown) {
for (GrCodeReferenceElement ref : refs) {
final PsiElement extendsElement = ref.resolve();
String canonicalText = ref.getCanonicalText();
boolean first = true;
if (extendsElement instanceof PsiClass) {
if (((PsiClass)extendsElement).isInterface()) {
interfaces.add(canonicalText);
}
else {
classes.add(canonicalText);
}
}
else {
unknown.add(canonicalText);
}
}
}
private void addNewClause(Collection<String> elements, Collection<String> additional, Project project, boolean isExtends) throws IncorrectOperationException {
if (elements.isEmpty() && additional.isEmpty()) return;
StringBuilder classText = new StringBuilder();
classText.append("class A ");
classText.append(isExtends ? "extends " : "implements ");
for (String str : elements) {
if (!first) classText += ", ";
classText += str;
first = false;
classText.append(str);
classText.append(", ");
}
// for (int i = 0; i < elements.size(); i++) {
// if (i > 0) classText += ", ";
//
// classText += elements.get(i);
// }
for (String str : additional) {
classText.append(str);
classText.append(", ");
}
classText += " {}";
classText.delete(classText.length() - 2, classText.length());
final GrTypeDefinition definition = GroovyPsiElementFactory.getInstance(project).createTypeDefinition(classText);
classText.append(" {}");
final GrTypeDefinition definition = GroovyPsiElementFactory.getInstance(project).createTypeDefinition(classText.toString());
GroovyPsiElement clause = isExtends ? definition.getExtendsClause() : definition.getImplementsClause();
assert clause != null;
myClass.getNode().addChild(clause.getNode(), myClass.getBody().getNode());
GrReferenceAdjuster.shortenReferences(clause);
PsiElement addedClause = myClass.addBefore(clause, myClass.getBody());
GrReferenceAdjuster.shortenReferences(addedClause);
}
public boolean startInWriteAction() {
@@ -1,108 +0,0 @@
/*
* Copyright 2000-2010 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.plugins.groovy.annotator.intentions;
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.psi.PsiReference;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.GroovyBundle;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrReferenceList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
/**
* @author Maxim.Medvedev
*/
public class ExtendsImplementsFix implements IntentionAction {
public static final ExtendsImplementsFix MOVE_TO_EXTENDS_LIST = new ExtendsImplementsFix(true);
public static final ExtendsImplementsFix MOVE_TO_IMPLEMENTS_LIST = new ExtendsImplementsFix(false);
private final boolean moveToExtendsList;
private ExtendsImplementsFix(boolean moveToExtendsList) {
this.moveToExtendsList = moveToExtendsList;
}
@NotNull
@Override
public String getText() {
return getFamilyName();
}
@NotNull
@Override
public String getFamilyName() {
return GroovyBundle.message(moveToExtendsList ? "move.to.extends.list" : "move.to.implements.list");
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return file.findReferenceAt(editor.getCaretModel().getOffset()) != null;
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
final PsiReference ref = file.findReferenceAt(editor.getCaretModel().getOffset());
PsiElement element = ref.getElement();
while (element.getParent() instanceof GrCodeReferenceElement) element = element.getParent();
final GrReferenceList oldRefList = ((GrReferenceList)element.getParent());
final GrTypeDefinition classParent = ((GrTypeDefinition)oldRefList.getParent());
GrReferenceList refList = moveToExtendsList ? classParent.getExtendsClause() : classParent.getImplementsClause();
if (refList == null) {
final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(project);
if (moveToExtendsList) {
refList = factory.createExtendsClause();
refList = (GrReferenceList)classParent.addAfter(refList, classParent.getNameIdentifierGroovy());
}
else {
refList = factory.createImplementsClause();
refList = (GrReferenceList)classParent.addAfter(refList, classParent.getExtendsClause());
}
}
refList.add(element.copy());
if (oldRefList.getReferenceElements().length==1) {
oldRefList.delete();
}
else {
final PsiElement prev = PsiUtil.skipWhitespacesAndComments(element.getPrevSibling(), false);
if (prev != null && prev.getNode().getElementType().equals(GroovyTokenTypes.mCOMMA)) {
prev.delete();
}
else {
final PsiElement next = PsiUtil.skipWhitespacesAndComments(element.getNextSibling(), false);
if (next != null && next.getNode().getElementType().equals(GroovyTokenTypes.mCOMMA)) {
next.delete();
}
}
element.delete();
}
}
@Override
public boolean startInWriteAction() {
return true;
}
}
@@ -0,0 +1,53 @@
package org.jetbrains.plugins.groovy.refactoring.implExtQuickFix
import com.intellij.openapi.application.ApplicationManager
import com.intellij.psi.PsiFile
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.plugins.groovy.annotator.intentions.ChangeExtendsImplementsQuickFix
import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition
import org.jetbrains.plugins.groovy.util.TestUtils
/**
* User: Dmitry.Krasilschikov
* Date: 11.10.2007
*/
public class ImplementsExtendsQuickFixTest extends LightCodeInsightFixtureTestCase {
final String basePath = TestUtils.testDataPath + "groovy/extendsImplementsFix/"
public void testClass1() { doTest() }
public void testExt1() { doTest() }
public void testImpl1() { doTest() }
public void testImpl2() { doTest() }
public void testImplext1() { doTest() }
public void testImplExt2() { doTest() }
public void testInterface1() { doTest() }
public void doTest() {
final List<String> data = TestUtils.readInput(testDataPath + getTestName(true) + ".test")
String fileText = data.get(0)
final PsiFile psiFile = TestUtils.createPseudoPhysicalGroovyFile(project, fileText)
assert psiFile instanceof GroovyFileBase
final GrTypeDefinition[] typeDefinitions = ((GroovyFileBase)psiFile).typeDefinitions
final GrTypeDefinition typeDefinition = typeDefinitions[typeDefinitions.length - 1]
String newText
if (typeDefinition.implementsClause == null && typeDefinition.extendsClause == null) {
newText = ""
}
else {
ApplicationManager.application.runWriteAction {
ChangeExtendsImplementsQuickFix fix = new ChangeExtendsImplementsQuickFix(typeDefinition)
fix.invoke(project, null, psiFile)
doPostponedFormatting(project)
} as Runnable
final GrTypeDefinition[] newTypeDefinitions = ((GroovyFileBase)psiFile).typeDefinitions
newText = newTypeDefinitions[newTypeDefinitions.length - 1].text
}
assertEquals(data.get(1), newText)
}
}
@@ -1,61 +0,0 @@
package org.jetbrains.plugins.groovy.refactoring.implExtQuickFix;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.psi.PsiFile;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
import org.jetbrains.plugins.groovy.annotator.intentions.ChangeExtendsImplementsQuickFix;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.util.TestUtils;
import java.util.List;
/**
* User: Dmitry.Krasilschikov
* Date: 11.10.2007
*/
public class ImplementsExtendsQuickFixTest extends LightCodeInsightFixtureTestCase {
@Override
protected String getBasePath() {
return TestUtils.getTestDataPath() + "groovy/extendsImplementsFix/";
}
public void testClass1() throws Throwable { doTest(); }
public void testExt1() throws Throwable { doTest(); }
public void testImpl1() throws Throwable { doTest(); }
public void testImpl2() throws Throwable { doTest(); }
public void testImplext1() throws Throwable { doTest(); }
public void testImplExt2() throws Throwable { doTest(); }
public void testInterface1() throws Throwable { doTest(); }
public void doTest() throws Exception {
final List<String> data = TestUtils.readInput(getTestDataPath() + getTestName(true) + ".test");
String fileText = data.get(0);
final PsiFile psiFile = TestUtils.createPseudoPhysicalGroovyFile(getProject(), fileText);
assert psiFile instanceof GroovyFileBase;
final GrTypeDefinition[] typeDefinitions = ((GroovyFileBase) psiFile).getTypeDefinitions();
final GrTypeDefinition typeDefinition = typeDefinitions[typeDefinitions.length - 1];
String newText;
if (typeDefinition.getImplementsClause() == null && typeDefinition.getExtendsClause() == null) {
newText = "";
} else {
new WriteCommandAction(getProject()) {
@Override
protected void run(Result result) throws Throwable {
ChangeExtendsImplementsQuickFix fix =
new ChangeExtendsImplementsQuickFix(typeDefinition.getExtendsClause(), typeDefinition.getImplementsClause());
fix.invoke(getProject(), null, psiFile);
}
}.execute();
final GrTypeDefinition[] newTypeDefinitions = ((GroovyFileBase) psiFile).getTypeDefinitions();
newText = newTypeDefinitions[newTypeDefinitions.length - 1].getText();
}
assertEquals(data.get(1), newText);
}
}
@@ -1,5 +1,3 @@
interface Runnable {
}
class B {
}
class A extends Runnable implements B {
@@ -4,5 +4,5 @@ interface C {}
interface D extends A implements B, C {
}
-----
interface D extends C {
interface D extends C, A, B {
}