0

I'm writing custom lint check to ban some methods. So if someone calls banned method foo on instance of class A, lint should report error.

I achieved this for static methods like this (inside visitCallExpression(UCallExpression):

node.receiver as UReferenceExpression).getQualifiedName()

From qualified name I can get the Class object and run my check but I can't get the qualified name for methods called on instantiated object. I can get the name of the class to which the objects belongs but not the qualified name.

How do I get qualified name of class of method that is called on instance of that class? If I'm being unclear, here is an example.

import android.view.Button;

class ButtonSetTextIntClass {
       private Button button;

       public void bannedSetText (){
            button.setText(123);
       }
}

And I need in visitCallExpression (UCallExpression) get qualified name/class of button.

lsrom
  • 608
  • 8
  • 22

3 Answers3

0

UCallExpression.receiverType does what you want:

public class CustomDetector extends Detector implements SourceCodeScanner {

    @Nullable
    @Override
    public List<Class<? extends UElement>> getApplicableUastTypes() {
        return Collections.singletonList(UCallExpression.class);
    }

    @Nullable
    @Override
    public UElementHandler createUastHandler(@NotNull JavaContext context) {
        return new UElementHandler() {

            @Override
            public void visitCallExpression(@NotNull UCallExpression node) {
                node.getReceiverType(); // PsiType:Button
            }
        };
    }
}

To extract qualified name you can use the following method:

((PsiClassType) node.getReceiverType()).resolve().getQualifiedName() // android.widget.Button
italankin
  • 779
  • 8
  • 14
  • Unfortunately this doesn't work with Kotlin top level functions. If you have top level function, which calls the banned method, this fails to get the qualified name of the banned method. – lsrom Jan 02 '19 at 11:38
0

I found a solution which works for both static and non-static methods and for Kotlin top level functions. Not sure if it is the best way to do it but at least it works.

override fun visitCallExpression(node: UCallExpression) {
     (node.resolve()?.parent as? ClsClassImpl)?.stub?.qualifiedName
}
lsrom
  • 608
  • 8
  • 22
0
    /**
     * eg: android.util.Log
     * ps. imports was initialized in visitImportStatement
     */
    private fun getClassNameWithPackage(node: UCallExpression): String {
        var className = node.resolve()?.containingClass?.qualifiedName
        if (className != null) {
            return className
        }

        className = getClassName(node) ?: return ""
        for (import in imports) {
            if (import.contains(className)) {
                return import
            }
        }
        return "$packageName.$className"
    }

    /**
     * eg: Log
     */
    private fun getClassName(node: UCallExpression): String? {
        return node.receiver?.javaPsi?.text ?: when (val uExpression = (node.methodIdentifier?.uastParent as UCallExpression?)?.receiver) {
            is JavaUSimpleNameReferenceExpression -> {
                uExpression.identifier
            }
            is KotlinUSimpleReferenceExpression -> {
                uExpression.identifier
            }
            is UReferenceExpression -> {
                uExpression.getQualifiedName()
            }
            else -> null
        }
    }
YouCii
  • 221
  • 2
  • 7