-
Notifications
You must be signed in to change notification settings - Fork 133
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* fixed: ObjCProtocol to NSObject promoter in case of private classes…
… and multiple constructors (#753) ## Promoter was working only in case one constructor is present. But in case of private classes there is additional synthetic public constructor is created. Code updated to ignore synthetic constructors and allow multiple constructors to be available ## Inspection Added Intellij Idea ispections to highlight following cases: - if class implements NSObjectProtocol and extends `java.lang.Object` -- warning with quick fix to extend from NSObject (actually this class will be promoted) - if class extends some class that implements NSObjectProtocol and extends `java.lang.Object` -- warning for both classes and quick fix to navigate to supper that can be updated with NSObject super. - if class implements protocol but extends class that has some hierarchy behind -- mark as error as this class will not be handled by promoter and will cause Runtime crash
- Loading branch information
Showing
10 changed files
with
445 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
.../idea/src/main/java/org/robovm/idea/inspection/ClassWithProtocolShouldExtendNSObject.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package org.robovm.idea.inspection; | ||
|
||
import com.intellij.codeInspection.*; | ||
import com.intellij.openapi.project.Project; | ||
import com.intellij.psi.JavaPsiFacade; | ||
import com.intellij.psi.PsiAnonymousClass; | ||
import com.intellij.psi.PsiClass; | ||
import com.intellij.psi.PsiIdentifier; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.robovm.idea.inspection.quickfix.ChangeSuperClassFix; | ||
import org.robovm.idea.inspection.quickfix.NavigateToClassFix; | ||
|
||
import java.util.*; | ||
|
||
/** | ||
* Simple inspection that check Java objects that implement NSObjectProtocol and | ||
* doesn't extend NSObject | ||
* @see #checkClass(PsiClass, InspectionManager, boolean) for comments | ||
* @author dkimitsa | ||
*/ | ||
public class ClassWithProtocolShouldExtendNSObject extends AbstractBaseJavaLocalInspectionTool { | ||
@Override | ||
public ProblemDescriptor[] checkClass(@NotNull PsiClass cls, @NotNull InspectionManager manager, boolean isOnTheFly) { | ||
try { | ||
// ignore: annotations, interfaces and anonymous classes | ||
if (cls instanceof PsiAnonymousClass || cls.isInterface() || cls.isAnnotationType()) | ||
return null; | ||
|
||
// skip if unable to get class name identifier and build the message | ||
PsiIdentifier classNameIdentifier = cls.getNameIdentifier(); | ||
if (classNameIdentifier == null) | ||
return null; | ||
|
||
// if extends from NSObject -- do nothing | ||
LinkedList<PsiClass> clsHierarchy = new LinkedList<>(); | ||
if (RvmCommonUtils.clsExtends(cls, RvmCommonClassNames.NSObject, clsHierarchy)) | ||
return null; | ||
|
||
// check hierarchy and if there is any class with @Marshaler annotation -- skip case | ||
for (PsiClass c : clsHierarchy) | ||
if (c.getAnnotation(RvmCommonClassNames.Marshaler) != null) | ||
return null; | ||
|
||
// move from bottom of hierarchy to find first class that implements NSObjectProtocol | ||
PsiClass clsWithProtocol = null; | ||
ListIterator<PsiClass> it = clsHierarchy.listIterator(clsHierarchy.size()); | ||
while (it.hasPrevious()) { | ||
PsiClass c = it.previous(); | ||
if (RvmCommonUtils.clsImplements(c, RvmCommonClassNames.NSObjectProtocol, true)) { | ||
clsWithProtocol = c; | ||
break; | ||
} | ||
} | ||
|
||
// if class doesn't implement NSProtocol -- do nothing | ||
if (clsWithProtocol == null || clsWithProtocol.getName() == null) | ||
return null; | ||
|
||
|
||
ProblemDescriptor[] problemDescriptors = null; | ||
if (clsWithProtocol == clsHierarchy.getLast()) { | ||
// class implements protocol and directly extends from java.lang.Object | ||
// this class will be promoted to NSObject. Attach warning. | ||
if (clsWithProtocol == cls) { | ||
// its same class under analyze, propose switch to NSObject supper as a quick-fix | ||
// show as warning | ||
Project project = cls.getProject(); | ||
PsiClass NSObjectCls = JavaPsiFacade.getInstance(project).findClass(RvmCommonClassNames.NSObject, | ||
cls.getResolveScope()); | ||
if (NSObjectCls != null) { | ||
LocalQuickFix fix = new ChangeSuperClassFix(NSObjectCls); | ||
String description = RvmInspectionBundle.message("robovm.inspection.objcprotocol.shouldbe.in.nsobject"); | ||
ProblemDescriptor p = manager.createProblemDescriptor(classNameIdentifier, | ||
description, fix, ProblemHighlightType.WARNING, false); | ||
problemDescriptors = new ProblemDescriptor[]{p}; | ||
} | ||
} else { | ||
// protocol was declared in one of the supers, but its fixable, propose navigate to | ||
// that super as quick fix | ||
// show as warning | ||
LocalQuickFix fix = new NavigateToClassFix(clsWithProtocol, clsWithProtocol.getName()); | ||
String description = RvmInspectionBundle.message("robovm.inspection.objcprotocol.parent.should.extend.nsobject", | ||
cls.getName(), clsWithProtocol.getName()); | ||
ProblemDescriptor p = manager.createProblemDescriptor(classNameIdentifier, | ||
description, fix, ProblemHighlightType.WARNING, false); | ||
problemDescriptors = new ProblemDescriptor[]{p}; | ||
} | ||
} else { | ||
// class has intermediate super between it and java.lang.Object and can't be promoted | ||
// the class can't be promoted and will cause error runtime | ||
String description; | ||
if (cls == clsWithProtocol) { | ||
description = RvmInspectionBundle.message("robovm.inspection.objcprotocol.should.extend.object.or.nsobject.error", | ||
cls.getName(), clsWithProtocol.getName()); | ||
} else { | ||
description = RvmInspectionBundle.message("robovm.inspection.objcprotocol.parent.should.extend.object.or.nsobject.error", | ||
cls.getName(), clsWithProtocol.getName()); | ||
} | ||
ProblemDescriptor p = manager.createProblemDescriptor(classNameIdentifier, | ||
description, (LocalQuickFix) null, ProblemHighlightType.ERROR, false); | ||
problemDescriptors = new ProblemDescriptor[]{p}; | ||
} | ||
|
||
return problemDescriptors; | ||
} catch (Exception e) { | ||
throw e; | ||
} | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
plugins/idea/src/main/java/org/robovm/idea/inspection/RvmCommonClassNames.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package org.robovm.idea.inspection; | ||
|
||
/** | ||
* set of RoboVM specific class names used for inspection | ||
*/ | ||
public final class RvmCommonClassNames { | ||
private RvmCommonClassNames() {} | ||
|
||
public static final String NSObject = "org.robovm.apple.foundation.NSObject"; | ||
public static final String NSObjectProtocol = "org.robovm.apple.foundation.NSObjectProtocol"; | ||
public static final String Marshaler = "org.robovm.rt.bro.annotation.Marshaler"; | ||
} |
71 changes: 71 additions & 0 deletions
71
plugins/idea/src/main/java/org/robovm/idea/inspection/RvmCommonUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package org.robovm.idea.inspection; | ||
|
||
import com.intellij.psi.*; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import java.util.List; | ||
|
||
public final class RvmCommonUtils { | ||
private RvmCommonUtils() { | ||
} | ||
|
||
/** | ||
* Checks if class extends specified class | ||
* | ||
* @param cls to test | ||
* @param classQN qualified name of class to check if extended | ||
*/ | ||
public static boolean clsExtends(@NotNull PsiClass cls, @NotNull String classQN) { | ||
return clsExtends(cls, classQN, null); | ||
} | ||
|
||
/** | ||
* Checks if class extends specified class | ||
* | ||
* @param cls to test | ||
* @param classQN qualified name of class to check if extended | ||
* @param outHierarchy if specified -- hierarchy of inheritance will be returned | ||
* excluding java.lang.Object and including cls itself | ||
*/ | ||
public static boolean clsExtends(@NotNull PsiClass cls, @NotNull String classQN, @Nullable List<PsiClass> outHierarchy) { | ||
if (outHierarchy != null) outHierarchy.add(cls); | ||
|
||
PsiClass superCls = cls.getSuperClass(); | ||
while (superCls != null && !CommonClassNames.JAVA_LANG_OBJECT.equals(superCls.getQualifiedName())) { | ||
if (outHierarchy != null) outHierarchy.add(superCls); | ||
if (classQN.equals(superCls.getQualifiedName())) | ||
return true; | ||
superCls = superCls.getSuperClass(); | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* checks if class implements Interface | ||
* @param cls to check | ||
* @param interfaceQN fully qualified name of interface | ||
* @param checkSubInterfaces deep check -- super of each interface will be chcekd | ||
* @return true if implements | ||
*/ | ||
public static boolean clsImplements(@NotNull PsiClass cls, @NotNull String interfaceQN, boolean checkSubInterfaces) { | ||
PsiClassType[] implementsList = cls.isInterface() ? cls.getExtendsListTypes() : cls.getImplementsListTypes(); | ||
for (PsiClassType implType : implementsList) { | ||
if (implType.equalsToText(interfaceQN)) { | ||
return true; | ||
} | ||
if (checkSubInterfaces) { | ||
// check sub-interfaces | ||
PsiClass el = implType.resolve(); | ||
if (el != null && clsImplements(el, interfaceQN, true)) | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public static PsiJavaCodeReferenceElement[] getReferences(PsiReferenceList list) { | ||
return list == null ? PsiJavaCodeReferenceElement.EMPTY_ARRAY : list.getReferenceElements(); | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
plugins/idea/src/main/java/org/robovm/idea/inspection/RvmInspectionBundle.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package org.robovm.idea.inspection; | ||
|
||
import com.intellij.DynamicBundle; | ||
import org.jetbrains.annotations.Nls; | ||
import org.jetbrains.annotations.NonNls; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.PropertyKey; | ||
|
||
import java.util.function.Supplier; | ||
|
||
public final class RvmInspectionBundle extends DynamicBundle { | ||
public static final @NonNls String BUNDLE = "robovm.RvmInspectionBundle"; | ||
|
||
private RvmInspectionBundle() { | ||
super(BUNDLE); | ||
} | ||
|
||
private static final DynamicBundle INSTANCE = new RvmInspectionBundle(); | ||
|
||
public static @NotNull | ||
@Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object ... params) { | ||
return INSTANCE.getMessage(key, params); | ||
} | ||
|
||
public static @NotNull Supplier<String> messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object ... params) { | ||
return INSTANCE.getLazyMessage(key, params); | ||
} | ||
} |
Oops, something went wrong.