From 6d8bfc9f50a1d9bebe7bf3354452af9f7d25022c Mon Sep 17 00:00:00 2001 From: Demyan Kimitsa Date: Mon, 9 Oct 2023 01:00:34 +0300 Subject: [PATCH] * 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 --- .../objc/ObjCProtocolToObjCObjectPlugin.java | 56 ++++----- ...ClassWithProtocolShouldExtendNSObject.java | 109 ++++++++++++++++++ .../idea/inspection/RvmCommonClassNames.java | 12 ++ .../idea/inspection/RvmCommonUtils.java | 71 ++++++++++++ .../idea/inspection/RvmInspectionBundle.java | 28 +++++ .../quickfix/ChangeSuperClassFix.java | 108 +++++++++++++++++ .../quickfix/NavigateToClassFix.java | 44 +++++++ .../src/main/resources/META-INF/plugin.xml | 15 ++- ...ClassWithProtocolShouldExtendNSObject.html | 22 ++++ .../robovm/RvmInspectionBundle.properties | 8 ++ 10 files changed, 445 insertions(+), 28 deletions(-) create mode 100644 plugins/idea/src/main/java/org/robovm/idea/inspection/ClassWithProtocolShouldExtendNSObject.java create mode 100644 plugins/idea/src/main/java/org/robovm/idea/inspection/RvmCommonClassNames.java create mode 100644 plugins/idea/src/main/java/org/robovm/idea/inspection/RvmCommonUtils.java create mode 100644 plugins/idea/src/main/java/org/robovm/idea/inspection/RvmInspectionBundle.java create mode 100644 plugins/idea/src/main/java/org/robovm/idea/inspection/quickfix/ChangeSuperClassFix.java create mode 100644 plugins/idea/src/main/java/org/robovm/idea/inspection/quickfix/NavigateToClassFix.java create mode 100644 plugins/idea/src/main/resources/inspectionDescriptions/ClassWithProtocolShouldExtendNSObject.html create mode 100644 plugins/idea/src/main/resources/robovm/RvmInspectionBundle.properties diff --git a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/objc/ObjCProtocolToObjCObjectPlugin.java b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/objc/ObjCProtocolToObjCObjectPlugin.java index 44ba9af89..b8c8bb977 100755 --- a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/objc/ObjCProtocolToObjCObjectPlugin.java +++ b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/objc/ObjCProtocolToObjCObjectPlugin.java @@ -22,15 +22,11 @@ import org.robovm.compiler.log.Logger; import org.robovm.compiler.plugin.AbstractCompilerPlugin; import org.robovm.compiler.plugin.CompilerPlugin; -import soot.Body; -import soot.PatchingChain; -import soot.SootClass; -import soot.SootMethod; -import soot.SootResolver; -import soot.Unit; +import soot.*; import soot.jimple.InvokeStmt; import soot.jimple.SpecialInvokeExpr; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -43,6 +39,7 @@ public class ObjCProtocolToObjCObjectPlugin extends AbstractCompilerPlugin { public static final String OBJC_PROTOCOL = "org.robovm.objc.ObjCProtocol"; public static final String NS_OBJECT = "org.robovm.apple.foundation.NSObject"; public static final String OBJC_OBJECT = "org.robovm.objc.ObjCObject"; + private static final int SYNTHETIC = 0x00001000; private SootClass org_robovm_apple_foundation_NSObject = null; private SootClass org_robovm_objc_ObjCObject = null; private SootClass org_robovm_objc_ObjCProtocol = null; @@ -106,36 +103,41 @@ private SootClass getSuperReplacementCandidate(SootClass cls) { * @return true if super call was adjusted */ private boolean adjustSuperInitCall(Logger logger, SootClass cls, SootClass newSuper) { - // replacing only java.lang.Object supers and there is expected to be only + // replacing only java.lang.Object super calls + // skipping synthetic constructors and constructors that calls "this". instead of supper // single kind of super call () so call directly List constructors = cls.getMethods().stream() - .filter((m) -> m.getName().equals("")) + .filter((m) -> (m.getModifiers() & SYNTHETIC) == 0 && m.getName().equals("")) .collect(Collectors.toList()); - if (constructors.size() == 1) { - SootMethod method = constructors.get(0); + // getting all expressions for modification + List expressionsToAdjust = new ArrayList<>(); + + for (SootMethod method: constructors) { + // find first SpecialInvokeExpr Body body = method.retrieveActiveBody(); PatchingChain units = body.getUnits(); - for (Unit unit = units.getFirst(); unit != null; unit = body.getUnits().getSuccOf(unit)) { - if (unit instanceof InvokeStmt) { - InvokeStmt invoke = (InvokeStmt) unit; - if (invoke.getInvokeExpr() instanceof SpecialInvokeExpr) { - // replace only call to Object() constructor - SpecialInvokeExpr expr = (SpecialInvokeExpr) invoke.getInvokeExpr(); - if (expr.getMethodRef().getSignature().equals("()>")) { - expr.setMethodRef(newSuper.getMethod("void ()").makeRef()); - return true; - } - } + SpecialInvokeExpr expr = units.stream() + .filter(e -> e instanceof InvokeStmt && ((InvokeStmt) e).getInvokeExpr() instanceof SpecialInvokeExpr) + .map(e -> (SpecialInvokeExpr) ((InvokeStmt) e).getInvokeExpr()) + .findFirst() + .orElse(null); + if (expr != null) { + if (expr.getMethodRef().getSignature().equals("()>")) { + expressionsToAdjust.add(expr); + } else if (expr.getMethodRef().declaringClass() != cls) { + // skip "this". calls but fails on other class init call + logger.warn("ObjCProtocol to NSObject failed: missing ()> call in %s", cls.getName()); + return false; } } - - // constructor was not substituted - logger.warn("ObjCProtocol to NSObject failed: missing ()> call in %s", cls.getName()); - } else { - logger.warn("ObjCProtocol to NSObject failed: too many public constructors"); } - return false; + // patch collected expression + SootMethodRef replacement = newSuper.getMethod("void ()").makeRef(); + for (SpecialInvokeExpr expr: expressionsToAdjust) + expr.setMethodRef(replacement); + + return true; } @Override diff --git a/plugins/idea/src/main/java/org/robovm/idea/inspection/ClassWithProtocolShouldExtendNSObject.java b/plugins/idea/src/main/java/org/robovm/idea/inspection/ClassWithProtocolShouldExtendNSObject.java new file mode 100644 index 000000000..9ba74c399 --- /dev/null +++ b/plugins/idea/src/main/java/org/robovm/idea/inspection/ClassWithProtocolShouldExtendNSObject.java @@ -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 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 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; + } + } +} diff --git a/plugins/idea/src/main/java/org/robovm/idea/inspection/RvmCommonClassNames.java b/plugins/idea/src/main/java/org/robovm/idea/inspection/RvmCommonClassNames.java new file mode 100644 index 000000000..5546d5caf --- /dev/null +++ b/plugins/idea/src/main/java/org/robovm/idea/inspection/RvmCommonClassNames.java @@ -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"; +} diff --git a/plugins/idea/src/main/java/org/robovm/idea/inspection/RvmCommonUtils.java b/plugins/idea/src/main/java/org/robovm/idea/inspection/RvmCommonUtils.java new file mode 100644 index 000000000..b30523bb8 --- /dev/null +++ b/plugins/idea/src/main/java/org/robovm/idea/inspection/RvmCommonUtils.java @@ -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 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(); + } +} diff --git a/plugins/idea/src/main/java/org/robovm/idea/inspection/RvmInspectionBundle.java b/plugins/idea/src/main/java/org/robovm/idea/inspection/RvmInspectionBundle.java new file mode 100644 index 000000000..65fe38801 --- /dev/null +++ b/plugins/idea/src/main/java/org/robovm/idea/inspection/RvmInspectionBundle.java @@ -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 messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object ... params) { + return INSTANCE.getLazyMessage(key, params); + } + } \ No newline at end of file diff --git a/plugins/idea/src/main/java/org/robovm/idea/inspection/quickfix/ChangeSuperClassFix.java b/plugins/idea/src/main/java/org/robovm/idea/inspection/quickfix/ChangeSuperClassFix.java new file mode 100644 index 000000000..5e81f6036 --- /dev/null +++ b/plugins/idea/src/main/java/org/robovm/idea/inspection/quickfix/ChangeSuperClassFix.java @@ -0,0 +1,108 @@ +package org.robovm.idea.inspection.quickfix; + +import com.intellij.codeInsight.FileModificationService; +import com.intellij.codeInsight.intention.HighPriorityAction; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.robovm.idea.inspection.RvmInspectionBundle; + +import java.util.Objects; + +import static org.robovm.idea.inspection.RvmCommonUtils.getReferences; + +/** + * dkimitsa: code based on com.intellij.compiler.inspection as direct use of it not + * possible as it seems to be internal class with unstable API + */ +public class ChangeSuperClassFix implements LocalQuickFix, HighPriorityAction { + @NotNull + private final SmartPsiElementPointer myNewSuperClass; + @NotNull + private final String myNewSuperName; + + public ChangeSuperClassFix( + @NotNull PsiClass newSuperClass + ) { + final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(newSuperClass.getProject()); + myNewSuperName = Objects.requireNonNull(newSuperClass.getQualifiedName()); + myNewSuperClass = smartPointerManager.createSmartPsiElementPointer(newSuperClass); + } + + @NotNull + @Override + public String getName() { + return RvmInspectionBundle.message("robovm.quickfix.make.extends", myNewSuperName); + } + + @NotNull + @Override + public String getFamilyName() { + return RvmInspectionBundle.message("robovm.inspection.group"); + } + + @Override + public boolean startInWriteAction() { + return false; + } + + @Override + public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor problemDescriptor) { + final PsiClass newSuperClass = myNewSuperClass.getElement(); + if (newSuperClass == null) return; + PsiClass aClass = PsiTreeUtil.getParentOfType(problemDescriptor.getPsiElement(), PsiClass.class, false); + if (aClass == null || !FileModificationService.getInstance().preparePsiElementsForWrite(aClass)) return; + changeSuperClass(aClass, newSuperClass); + } + + /** + * newSuperClass can be interfaces or classes in any combination + *

+ * 1. does not check that newSuperClass not exists in currently existed supers + */ + private static void changeSuperClass(@NotNull final PsiClass aClass, + @NotNull final PsiClass newSuperClass) { + WriteAction.run(() -> addSuperClass(aClass, newSuperClass)); + } + + private static void addSuperClass( + @NotNull final PsiClass aClass, + @NotNull final PsiClass newSuperClass + ) { + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(aClass.getProject()); + final PsiClass oldSuperClass = aClass.getSuperClass(); + if (oldSuperClass == null || aClass instanceof PsiAnonymousClass) + return; + + PsiElementFactory factory = psiFacade.getElementFactory(); + PsiReferenceList extendsList = Objects.requireNonNull(aClass.getExtendsList()); + PsiJavaCodeReferenceElement[] refElements = getReferences(extendsList); + for (PsiJavaCodeReferenceElement refElement : refElements) { + if (refElement.isReferenceTo(oldSuperClass)) { + refElement.delete(); + } + } + + PsiReferenceList list; + if (newSuperClass.isInterface() && !aClass.isInterface()) { + list = aClass.getImplementsList(); + } else { + list = extendsList; + PsiJavaCodeReferenceElement[] elements = list.getReferenceElements(); + if (elements.length == 1) { + PsiClass objectClass = psiFacade.findClass(CommonClassNames.JAVA_LANG_OBJECT, aClass.getResolveScope()); + if (objectClass != null && elements[0].isReferenceTo(objectClass)) { + elements[0].delete(); + } + } + } + assert list != null; + PsiElement ref = list.add(factory.createClassReferenceElement(newSuperClass)); + JavaCodeStyleManager.getInstance(aClass.getProject()).shortenClassReferences(ref); + } +} diff --git a/plugins/idea/src/main/java/org/robovm/idea/inspection/quickfix/NavigateToClassFix.java b/plugins/idea/src/main/java/org/robovm/idea/inspection/quickfix/NavigateToClassFix.java new file mode 100644 index 000000000..52e5ce99a --- /dev/null +++ b/plugins/idea/src/main/java/org/robovm/idea/inspection/quickfix/NavigateToClassFix.java @@ -0,0 +1,44 @@ +package org.robovm.idea.inspection.quickfix; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.util.IntentionFamilyName; +import com.intellij.codeInspection.util.IntentionName; +import com.intellij.openapi.project.Project; +import com.intellij.pom.Navigatable; +import org.jetbrains.annotations.NotNull; +import org.robovm.idea.inspection.RvmInspectionBundle; + +/** + * Navigate to class QuickFix + * @author dkimitsa + */ +public class NavigateToClassFix implements LocalQuickFix { + @NotNull + private final Navigatable element; + @NotNull + private final String elementName; + + public NavigateToClassFix(@NotNull Navigatable element, @NotNull String elementName) { + this.element = element; + this.elementName = elementName; + } + + @Override + public @IntentionName + @NotNull String getName() { + return RvmInspectionBundle.message("robovm.quickfix.navigate.tofix", elementName); + } + + @Override + public @IntentionFamilyName + @NotNull String getFamilyName() { + return RvmInspectionBundle.message("robovm.inspection.group"); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + if (element.canNavigateToSource()) + element.navigate(true); + } +} diff --git a/plugins/idea/src/main/resources/META-INF/plugin.xml b/plugins/idea/src/main/resources/META-INF/plugin.xml index db26c4c3d..8ed377c67 100755 --- a/plugins/idea/src/main/resources/META-INF/plugin.xml +++ b/plugins/idea/src/main/resources/META-INF/plugin.xml @@ -6,7 +6,7 @@ org.jetbrains.idea.maven org.jetbrains.plugins.gradle com.intellij.modules.java - + MobiVM @@ -61,6 +61,19 @@ + + + + diff --git a/plugins/idea/src/main/resources/inspectionDescriptions/ClassWithProtocolShouldExtendNSObject.html b/plugins/idea/src/main/resources/inspectionDescriptions/ClassWithProtocolShouldExtendNSObject.html new file mode 100644 index 000000000..99db57be3 --- /dev/null +++ b/plugins/idea/src/main/resources/inspectionDescriptions/ClassWithProtocolShouldExtendNSObject.html @@ -0,0 +1,22 @@ + + +Reports classes that implements NSObjectProtocol and doesn't extend from NSObject + +Objc/Bro code will not be able to marshal such pure Java object to ObjC/Native side. +(and will crash with message: CLASSNAME cannot be cast to org.robovm.objc.ObjCObject) +While there is Object to NSObject promoter plugin that will replace super with NSObject +during RoboVM compilation phase, it's always better to have it clearly specified in source code. +

Example:

+

+    // will be reported as warning with option to extend from NSObject
+    class Foo implements NSObjectProtocol {}
+
+    // will be reported as error as Foo doesn't extend from java.lang.Object directly
+    class Foo extends ArrayList implements NSObjectProtocol {}
+
+ +

+ This inspection is intended for RoboVM application that use ObjC bindings. +

+ + \ No newline at end of file diff --git a/plugins/idea/src/main/resources/robovm/RvmInspectionBundle.properties b/plugins/idea/src/main/resources/robovm/RvmInspectionBundle.properties new file mode 100644 index 000000000..ae07419fb --- /dev/null +++ b/plugins/idea/src/main/resources/robovm/RvmInspectionBundle.properties @@ -0,0 +1,8 @@ + +robovm.inspection.group=RoboVM inspections +robovm.inspection.objcprotocol.shouldbe.in.nsobject=Class that implements any NSObjectProtocol should extend NSObject +robovm.inspection.objcprotocol.parent.should.extend.nsobject=Class ''{0}'' extends ''{1}'' that implements NSObjectProtocol but does not extend NSObject +robovm.inspection.objcprotocol.parent.should.extend.object.or.nsobject.error=Class ''{0}'' extends ''{1}'' that implements NSObjectProtocol but does not extend NSObject or Object directly and can not be promoted to NSObject during RoboVM compilation. It will cause runtime crash when object is used with ObjC code. +robovm.inspection.objcprotocol.should.extend.object.or.nsobject.error=Class ''{0}'' that implements NSObjectProtocol (or its subclass) but does not extend NSObject or Object directly and can not be promoted to NSObject during RoboVM compilation. It will cause runtime crash when object is used with ObjC code. +robovm.quickfix.make.extends=Extend from ''{0}'' +robovm.quickfix.navigate.tofix=Navigate to ''{0}''